反射修改jsessionid实现Session共享
其实,原生的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 -》manager
的findSession
方法直接获取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);
因此可以有如下两条思路:
- 通过findSession()直接从ConcurrentHashMap中获取Session对象
- 通过对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共享相关推荐
- C#session共享+redis_Shiro权限管理框架(二):Shiro结合Redis实现分布式环境下的Session共享...
精品推荐 国内稀缺优秀Java全栈课程-Vue+SpringBoot通讯录系统全新发布! Docker快速手上视频教程(无废话版)[免费] 作者:夜月归途 转载自: https://www.cnblo ...
- JSESSIONID、SESSION、cookie
所谓session可以这样理解:当与服务端进行会话时,比如说登陆成功后,服务端会为用户开壁一块内存区间,用以存放用户这次会话的一些内容,比如说用户名之类的.那么就需要一个东西来标志这个内存区间是你的而 ...
- 集群间实现Session共享
上一篇,同一tomcat不同项目下session共享方案:http://blog.csdn.net/qinmengdecluntan/article/details/72832648 一.引言 针对企 ...
- 分布式中使用 Redis 实现 Session 共享(中)
http://blog.jobbole.com/91874/ 原文出处: 焰尾迭 欢迎分享原创到伯乐头条 上一篇介绍了一些redis的安装及使用步骤,本篇开始将介绍redis的实际应用场景,先从最 ...
- Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享
Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...
- Nginx——Session共享
文章目录 其他文章 1.Session共享 1.1.session 一致性解决方案 1.1.1.session 复制 1.1.2.共享 session 1.2.安装memcached 1.2.1. n ...
- Spring Session Redis实现Session共享
Spring Session & Redis实现Session共享 前后端分离Session.单点登录实现 Session 共享简单方案,充当笔记 一.Session和Cookie 1.Ses ...
- java多域名共享session_同一服务器不同域名session共享
Tomcat下,不同的二级域名之间或根域与子域之间,Session默认是不共享的,因为Cookie名称为JSESSIONID的Cookie根域是默认是没设置 的,访问不同的二级域名,其Cookie就重 ...
- nginx+tomcat+memcache实现负载均衡、session共享
实验架构图: Table of Contents 1.配置tomcat 2.安装memcache 3.查看tomcat和memcache是否配置好 4.nginx实现负载均衡: 5.客户端进行测试: ...
- Redis + Tomcat + Nginx 集群实现 Session 共享
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者 | 蕃薯耀 链接 | www.cnblogs.com/fan ...
最新文章
- 【微信小程序企业级开发教程】界面跳转方法总结
- 机器人编程语言python-入门篇丨使用EV3机器人,趣味学习Python编程语言~
- 3、MySQL查看存储过程
- 永劫无间为啥显示连接服务器失败,永劫无间服务器故障怎么办?永劫无间服务器故障解决办法...
- NSOperation的使用细节 [3]
- 如何将FAT32分区直接转为NTFS分区
- linux 指定jvm djava.library.path_JVM命令
- 鼠标左键长按功能的实现
- TPL中的task并不是thread
- MAC编译OpenJDK8:error: ‘‘ within ‘||‘ [-Werror,-Wlogical-op-parentheses]
- Rust: format!
- cimoc 最新版_Cimoc1.49版下载
- 点餐系统小程序c语言,点餐系统小程序
- 在360与腾讯过家家时,我们该醒醒了
- android inflate,android inflate详解
- 2012年财富世界500强排行榜
- SpringBoot 就这一篇全搞定
- 2019「友盟杯」数据分析大赛结果公布
- 七巧板复原算法之小结局——给出一个最大结果集
- 系统软件和应用软件的划分
热门文章
- 烽火HG680-KA机顶盒 卡开机界面、救砖、强刷失败、ADB教程及固件分享
- RapidMiner
- Android MeasureSpec详解
- Odin靶机WriteUp
- 计算机快速录入,如何快速把书中的文字扫描并录入电脑
- eyb:Java代码通过FastDFS实现文件上传
- Android5.0系统 知识点总结(持续更新)
- iperf 服务端发送数据_iperf 流量测试
- 阅读ArrayList源码的一些记录
- 【阅读笔记】Implementation of tactical maneuvers with maneuver libraries