其实,原生的API是不行的。(不过文章末尾我在通读源码之后用反射写了一个Session共享的方式,需要的可以直接使用

  • 虽然说前端可以通过伪造Cookie中的JESESSIONID来模拟劫持
  • 而后端并不能直接通过原生API成功修改JESSIONID获取指定session。
  • 本案例一共12000字,源码分析部分10000字左右

0.Session域的大体流程

先大概了解一下为了创建Session会话域,原生API做了什么

  • 存在一个接口:HttpServletRequest,有实现类Request、RequestFacade(外观模式,也是实际请求对象)
  • Session对象维护在Request中
  • Request对象维护在RequestFacade中

0.1第一次:创建Session

  • JSESSIONID是在请求打过来的时候初始化的时候,用ServerCookie对Request进行了赋值(Request类是HttpServletRequest的实现类)
  • 然后创建一个供用户使用的Cookie(实际上是ServerCookie的拷贝值、ServerCookie不允许用户使用
  • 请求打过来的初始化过程就对Request对象的Session和jsessionid属性进行了绑定
  • Request对象的Session和jsessionid属性(主要是这两个)在很多地方都起着互相校验的功能(修改时需要两个一起修改,否则会导致request.getSession()时重新生成Session

0.2第二次:获取Session

  • ServerCookie中携带的jseesionid赋值给Request对象
  • 请求打过来时被预先处理,调用request -》context -》managerfindSession方法直接获取Session后传给Request对象当形参
  • 之后使用request.getSession()调用则直接调用Request的属性地址

下面进行论述

1.前端设置Cookie再发送

那么正常情况下我们切换浏览器(切换Session)是不能够获取这个k-v对的,但是我们可以通过在浏览器、PostMan、js代码将JSESSIONID设置为刚才的
这样就能在服务器中劫持Session了但是实际开发环境中,这种方案可行度接近于0,那么尝试下后端原生API修改Cookie的值??

2.后端设置Cookie值

2.1输出JSESSIONID

Cookie[] cookies = request.getCookies();if(cookies!=null)
for(Cookie c: cookies){//此案例中Cookie仅包含JSESIONID,等价于(request.getSession().getId())System.out.println(c.getName()+":"+c.getValue());
}

2.2修改JSESSIONID

request.getCookies()[0].setValue("newJSessionId");

2.3getSession()不变

//还是之前的JSESSIONID
System.out.println(request.getSession().getId());

3.后端为什么不能修改Session

3.1虽然后端是可以修改Cookie的

比如在使用
request.getCookies()[0].setValue("newJSessionId");
修改了Cookie之后,重新获取的Cookie确实是最新修改后的

    Cookie[] cookies2 = request.getCookies();if(cookies!=null)for(Cookie c: cookies2){System.out.println(c.getName()+":"+c.getValue());}

但是出了另外一个问题,Cookie能修改,Cookie中的JESSIONID修改后却没有用?

3.2修改JSESSIONID但无效

3.2.1debug:getSession()

一直跟进request.getSession()方法,发现修改Cookie中JSESSIONID之前和之后都是在这个地方返回的session对象,证明在getSession()之前,session一定被初始化了,修改Cookie不影响直接获取session

  • 整个过程没有看到任何跟Cookie中JSESSIONID有关的代码

  • 因此可以大胆推测:session对象早在请求打过来的时候就获取了其对应的地址值,后期修改Cookie不会导致session对象指向的地址改变

4.session初始化过程(多次请求的角度)

既然session不能被中途修改,那么一定是在初始化的时候,从Cookie中取出JSESSIONID,然后赋值给的session

4.1利用debug技巧定位

一个小技巧,可以在session对象上面打一个断点,每次(按F9)执行到修改session值的时候都会进入

4.2模拟创建Session

  • 现在代码里写一个request.getSession();然后新开一个浏览器请求两次,模拟创建Session和寻找Session的过程
  • Session的使用是懒汉式的,如果你不调getSession方法,他就不会去创建,更不会在响应Cookie里面携带JSESSIONID
  • Session的创建是懒汉,但并不意味着有关Session的配置信息的初始化是懒汉的

4.2.1创建Session

4.2.2寻找Session

4.2.3释放

释放环节,为下一次请求做准备

4.3模拟第二次请求Session

浏览器刷新一下请求

4.3.1直接寻找Session

这次不需要创建,而是直接根据requestedSesionId来寻找,然后将其维护在request的session属性中

4.3.2释放

再次释放资源

4.4小结

Session的创建,寻找都是在doGetSession(boolean create)方法中完成的。

5.getSession的实现(多次调用的角度)

由上述可知,getSession()最终调用的逻辑一定是doGetSession(),验证一下

  • 应该找与Request同包的,里面只有两个

5.1基本结构

5.1.1外观模式Facade

  • Facade是外观模式,最终的调用者肯定都是request

5.1.2Request中的getSession(create)

这里明显有个create,决定这个方法是获取还是创建

  • 明显空参的是使用的时候调
  • 带参的一定带false参数,在初始化的时候调

5.2一次请求,多次getSession()

在接口中写下代码模拟调用两次getSession()。现在仅模拟第二次请求(不需要再去创建Session,只需要去寻找Session)

HttpSession session1 = request.getSession();
HttpSession sessio2n = request.getSession();

通过debug可以知道

  • session1 需要先findSession把session交给request去维护,然后从request中取session对象
  • 而sessio2n 只需要直接从request中取session对象

5.3小结

通过上面的案例可以侧面反映出大标题4中为什么最后需要进行资源释放

6.Session流程总结

6.1创建和获取都是懒汉式的

6.1.1创建懒汉式

  • 如果每次请求都不需要getSession(),那么Session永不创建Cookie中的JSESSIONID永不创建

6.1.2获取懒汉式

  • 如果一次请求中多次使用getSession(),那么只有第一次会涉及createSession、findSession、request.session获取
  • 接下来每一次都只直接request.session获取

6.2路径变量历史遗留问题

早期应该是可以通过url上指定jsessionid来设置的,现在会覆盖掉导致设置失效

6.3创建、数据预置的关系

  • 其实在CoyoteAdapter类的后置初始化过程中,会将jsessionid写入request,至于你用不用session完全取决于自己。
  • 这个过程是数据预置,预置了也不一定会创建or获取

那么问题现在转成了这个requestSessionId是如何被初始化的

7.CoyoteAdapter(初始化过程)

适配器模式的 Adapter接口的一个实现类
观察里面的方法,并在service postParseRequest的开头分别打上断点
其中service(核心业务)先将Requset对象进行创建,
然后postParseRequest(后置处理)将req写入request

7.1postParseRequest()

这个方法名结合注释可知:解析完request之后必须要做的一些事。在开头打个断点然后发请求

7.1.1request对象赋值

在执行postParseRequest()之前,request对象中还没有值,所有的请求的值都存在于org.apache.coyote.Request req中 执行完成之后就将请求参数赋值给了request

  • 注意这三个的顺序!
  • 后面经分析我认为这个parsePathParameters解析路径变量是一个历史遗留问题,以前应该是支持的,现在无论如何都会被下面的逻辑覆盖

7.1.2先parsePathParameters解析路径变量

按这种url格式发送http://localhost:8088/pic/people/pic1;jsessionid=F0D3200C7D61C8D41E129924585B6234

这里通过解析request.getParam 用这个枚举类,即解析路径变量中的jessesionid 然后赋值给request

7.1.3然后parseSessionCookiesId

  • 这里并没有将Cookie赋值给request,而是直接从ServerCookie中取值JSESSIONID塞给request
  • 并且这个方法是在7.1.1之后执行的,也就是即便是路径变量中写了这个参数也会被覆盖

7.1.3被覆盖的路径变量jsessionid(历史遗留问题)

http://localhost:8088/pic/people/pic1;jsessionid=F0D3200C7D61C8D41E129924585B623这样的写法,会导致:

  • 先修改成功request中的sessionid
  • 然后再次被ServerCookie中的sessionid所覆盖
  • 然后再用这个sessionid去获取Session对象

7.2小结

7.2.1后端修改为何没用

正是因为JSESSIONID是在这个CoyoteAdapter类中的postParseRequest()方法中的parseSessionCookiesId()方法中就对JSESSIONID进行了赋值,导致如下情况:

  • 前端请求可以通过修改Cookie中的JSESSIONID来实现获取指定session
  • 但后端不能通过request在代码中修改Cookie中的JSESSIONID来实现获取指定session,也不能指定路径变量jsessionid(可能是历史遗留问题,会被覆盖)

7.2.2Cookie和ServerCookie

很明显这个JSESSIONID是从ServerCookie中获取的,那二者有什么区别呢?

ServerCookie来自于coyoteRequest,即tomcat的底层实现,因此不能针对其Cookie进行修改从而达到模拟session的效果

8.反射

因为request.getSession()调用的是Reuqest类下的doGetSession()requestedSessionId又是Request下的属性,那么能不能通过反射修改requestedSessionId的值来修改findSession()的结果呢?答案是可以

8.0撸组件的前提知识

  • 通常作为形参进行传递的HttpServletRequest是一个接口,实际上传递是是其实现类RequestFacade,在涉及到反射操作的时候需要再开辟一个RequestFacade的栈空间指向HttpServletRequest堆空间
  • 外观模式:RequestFacade实际上大部分调用的是Request
  • Request下的doGetSession(Boolean craete)是最终获取Session对象的方法,包括初始化时session的生成、httpServletRequest.getSeesion()获取。区别是前者形参create=true、后者形参create=false
  • 在Request类下的doGetSession()方法中,当存在session时直接走manager.findSession(sessionId)当初始化时,先getRequestedSessionId()根据JVM随机数获取sessionid,然后作为形参调用session = manager.createSession(sessionId);

因此可以有如下两条思路:

  1. 通过findSession()直接从ConcurrentHashMap中获取Session对象
  2. 通过对doGetSession()的所需的参数进行一个覆盖操作

8.1根据JSESSIONID直接返回Session对象

   /*** @param httpServletRequest 多态性,本质是requestFacade* @param jsessionid 指定的jsessionid* @return org.apache.catalina.Session 指定的Session对象**/public static Session getSession(HttpServletRequest httpServletRequest,String jsessionid) throws  Exception{RequestFacade requestFacade = (RequestFacade)httpServletRequest;Class<? extends RequestFacade> facadeClazz = requestFacade.getClass();Field requestField = facadeClazz.getDeclaredField("request");requestField.setAccessible(true);Request request = (Request)requestField.get(requestFacade);Context context = request.getContext();Manager manager = context.getManager();Session session = manager.findSession(jsessionid);return session;}

8.2将指定的Session和JSESSIONID赋值给request

/*** @Description 调用此方法后,httpServletRequest.getSession()获取的是jsessionid对应的Session* @Author zjh* @Date 17:59 2022/8/3* @param httpServletRequest 请求头,用于获取session* @param jsessionid 指定的sessionid* @return void**/
public static void setSession2Request(HttpServletRequest httpServletRequest,String jsessionid) throws Exception{//RequestFacadeRequestFacade requestFacade = (RequestFacade) httpServletRequest;Class<? extends RequestFacade> facadeClazz = requestFacade.getClass();//RequestFacade获取RequestField requestField = facadeClazz.getDeclaredField("request");requestField.setAccessible(true);//RequestRequest request = (Request)requestField.get(requestFacade);Class<? extends Request> requestClazz = request.getClass();//Request设置Session=nullField sessionField = requestClazz.getDeclaredField("session");sessionField.setAccessible(true);sessionField.set(request,null);//Request设置requestedSessionId = 指定值Field requestedSessionIdField = requestClazz.getDeclaredField("requestedSessionId");requestedSessionIdField.setAccessible(true);requestedSessionIdField.set(request,jsessionid);
}

8.3最终工具类

经过异常处理,但不处理空指针异常(jsessionid写错或者session过期)

import java.io.IOException;
import java.lang.reflect.Field;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.catalina.Context;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;/*** @Description Session共享的工具类。不对空指针进行处理,空指针就是jsessionid写错了或者过期* @Author zjh* @Date 16:10 2022/8/3**/
public class SessionShareUtils {private static Logger logger = LoggerFactory.getLogger("SessionShareUtils");/*** 获取指定的Session对象** @param httpServletRequest 多态性,本质是requestFacade* @param jsessionid 指定的jsessionid* @return HttpSession 指定的Session对象**/public static HttpSession getSession(HttpServletRequest httpServletRequest, String jsessionid) {HttpSession session = null;try {RequestFacade                  requestFacade = (RequestFacade) httpServletRequest;Class<? extends RequestFacade> facadeClazz   = requestFacade.getClass();//获取Request 最后findSessionField requestField = facadeClazz.getDeclaredField("request");requestField.setAccessible(true);Request request     = (Request) requestField.get(requestFacade);Context context     = request.getContext();Manager manager     = context.getManager();Session baseSession = manager.findSession(jsessionid);session = baseSession.getSession();}catch (ReflectiveOperationException e) {logger.error("反射异常{}", e);}catch (IOException e) {logger.error("findSession的IO异常{}", e);}catch (NullPointerException e) {logger.error("没找到对应的Session{}", e);}return session;}/*** 获取指定的Session对象** @param jsessionid 指定的jsessionid* @return HttpSession 指定的Session对象**/public static HttpSession getSession(String jsessionid) {HttpServletRequest httpServletRequest =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();HttpSession session = null;try {RequestFacade                  requestFacade = (RequestFacade) httpServletRequest;Class<? extends RequestFacade> facadeClazz   = requestFacade.getClass();//获取Request 最后findSessionField requestField = facadeClazz.getDeclaredField("request");requestField.setAccessible(true);Request request     = (Request) requestField.get(requestFacade);Context context     = request.getContext();Manager manager     = context.getManager();Session baseSession = manager.findSession(jsessionid);session = baseSession.getSession();}catch (ReflectiveOperationException e) {logger.error("反射异常{}", e);}catch (IOException e) {logger.error("findSession的IO异常{}", e);}catch (NullPointerException e) {logger.error("没找到对应的Session{}", e);}return session;}/*** @Description 调用此方法后,httpServletRequest.getSession()获取的是jsessionid对应的Session* @param httpServletRequest 请求头,用于获取session* @param jsessionid 指定的sessionid* @return void**/public static void setSession2Request(HttpServletRequest httpServletRequest, String jsessionid) {try {//RequestFacadeRequestFacade                  requestFacade = (RequestFacade) httpServletRequest;Class<? extends RequestFacade> facadeClazz   = requestFacade.getClass();Field                          requestField  = facadeClazz.getDeclaredField("request");requestField.setAccessible(true);//RequestRequest                  request      = (Request) requestField.get(requestFacade);Class<? extends Request> requestClazz = request.getClass();//Request设置Session=nullField sessionField = requestClazz.getDeclaredField("session");sessionField.setAccessible(true);sessionField.set(request, null);//Request设置requestedSessionId = 指定值Field requestedSessionIdField = requestClazz.getDeclaredField("requestedSessionId");requestedSessionIdField.setAccessible(true);requestedSessionIdField.set(request, jsessionid);}catch (ReflectiveOperationException e) {logger.error("反射异常{}", e);}}/*** @param key 指定Session中的key值* @return java.lang.Object value值* @Description 获取jsessionid中对应key的Session属性值**/public static Object getAttribute(String jsessionid, String key) {HttpSession session = getSession(jsessionid);Object      value   = session.getAttribute(key);return value;}}

反射修改jsessionid实现Session共享相关推荐

  1. C#session共享+redis_Shiro权限管理框架(二):Shiro结合Redis实现分布式环境下的Session共享...

    精品推荐 国内稀缺优秀Java全栈课程-Vue+SpringBoot通讯录系统全新发布! Docker快速手上视频教程(无废话版)[免费] 作者:夜月归途 转载自: https://www.cnblo ...

  2. JSESSIONID、SESSION、cookie

    所谓session可以这样理解:当与服务端进行会话时,比如说登陆成功后,服务端会为用户开壁一块内存区间,用以存放用户这次会话的一些内容,比如说用户名之类的.那么就需要一个东西来标志这个内存区间是你的而 ...

  3. 集群间实现Session共享

    上一篇,同一tomcat不同项目下session共享方案:http://blog.csdn.net/qinmengdecluntan/article/details/72832648 一.引言 针对企 ...

  4. 分布式中使用 Redis 实现 Session 共享(中)

    http://blog.jobbole.com/91874/ 原文出处: 焰尾迭   欢迎分享原创到伯乐头条 上一篇介绍了一些redis的安装及使用步骤,本篇开始将介绍redis的实际应用场景,先从最 ...

  5. Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享

    Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...

  6. Nginx——Session共享

    文章目录 其他文章 1.Session共享 1.1.session 一致性解决方案 1.1.1.session 复制 1.1.2.共享 session 1.2.安装memcached 1.2.1. n ...

  7. Spring Session Redis实现Session共享

    Spring Session & Redis实现Session共享 前后端分离Session.单点登录实现 Session 共享简单方案,充当笔记 一.Session和Cookie 1.Ses ...

  8. java多域名共享session_同一服务器不同域名session共享

    Tomcat下,不同的二级域名之间或根域与子域之间,Session默认是不共享的,因为Cookie名称为JSESSIONID的Cookie根域是默认是没设置 的,访问不同的二级域名,其Cookie就重 ...

  9. nginx+tomcat+memcache实现负载均衡、session共享

    实验架构图: Table of Contents 1.配置tomcat 2.安装memcache 3.查看tomcat和memcache是否配置好 4.nginx实现负载均衡: 5.客户端进行测试: ...

  10. Redis + Tomcat + Nginx 集群实现 Session 共享

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者 | 蕃薯耀 链接 | www.cnblogs.com/fan ...

最新文章

  1. 【微信小程序企业级开发教程】界面跳转方法总结
  2. 机器人编程语言python-入门篇丨使用EV3机器人,趣味学习Python编程语言~
  3. 3、MySQL查看存储过程
  4. 永劫无间为啥显示连接服务器失败,永劫无间服务器故障怎么办?永劫无间服务器故障解决办法...
  5. NSOperation的使用细节 [3]
  6. 如何将FAT32分区直接转为NTFS分区
  7. linux 指定jvm djava.library.path_JVM命令
  8. 鼠标左键长按功能的实现
  9. TPL中的task并不是thread
  10. MAC编译OpenJDK8:error: ‘‘ within ‘||‘ [-Werror,-Wlogical-op-parentheses]
  11. Rust: format!
  12. cimoc 最新版_Cimoc1.49版下载
  13. 点餐系统小程序c语言,点餐系统小程序
  14. 在360与腾讯过家家时,我们该醒醒了
  15. android inflate,android inflate详解
  16. 2012年财富世界500强排行榜
  17. SpringBoot 就这一篇全搞定
  18. 2019「友盟杯」数据分析大赛结果公布
  19. 七巧板复原算法之小结局——给出一个最大结果集
  20. 系统软件和应用软件的划分

热门文章

  1. 烽火HG680-KA机顶盒 卡开机界面、救砖、强刷失败、ADB教程及固件分享
  2. RapidMiner
  3. Android MeasureSpec详解
  4. Odin靶机WriteUp
  5. 计算机快速录入,如何快速把书中的文字扫描并录入电脑
  6. eyb:Java代码通过FastDFS实现文件上传
  7. Android5.0系统 知识点总结(持续更新)
  8. iperf 服务端发送数据_iperf 流量测试
  9. 阅读ArrayList源码的一些记录
  10. 【阅读笔记】Implementation of tactical maneuvers with maneuver libraries