• 反射修改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 -》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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.2修改JSESSIONID

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

    2.3getSession()不变

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

    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());
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    但是出了另外一个问题,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();
    
    • 1
    • 2

    通过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 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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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{
        //RequestFacade
        RequestFacade requestFacade = (RequestFacade) httpServletRequest;
        Class facadeClazz = requestFacade.getClass();
        //RequestFacade获取Request
        Field requestField = facadeClazz.getDeclaredField("request");
        requestField.setAccessible(true);
        //Request
        Request request = (Request)requestField.get(requestFacade);
        Class requestClazz = request.getClass();
        //Request设置Session=null
        Field 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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 facadeClazz   = requestFacade.getClass();
                //获取Request 最后findSession
                Field 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 facadeClazz   = requestFacade.getClass();
                //获取Request 最后findSession
                Field 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 {
                //RequestFacade
                RequestFacade                  requestFacade = (RequestFacade) httpServletRequest;
                Class facadeClazz   = requestFacade.getClass();
                Field                          requestField  = facadeClazz.getDeclaredField("request");
                requestField.setAccessible(true);
                //Request
                Request                  request      = (Request) requestField.get(requestFacade);
                Class requestClazz = request.getClass();
                //Request设置Session=null
                Field 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;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
  • 相关阅读:
    python中的range函数|python中的range函数|range()函数详解|Python中range(len())的用法
    会议OA项目-其它页面->自定义组件应用,其它界面的布局
    全国矿产地空间分布数据
    【Spring Boot】Web开发 — Web开发简介
    SpirngMVC获取请求参数
    mysql实战操作总结
    项目管理之如何出道(中)
    C++常成员函数 - const 关键字
    Python gdal读取MODIS遥感影像并结合质量控制QC波段掩膜数据
    最强辅助!阿里最新总结22年大厂Java面试真题+核心知识点全面覆盖+答案详解!
  • 原文地址:https://blog.csdn.net/m0_56079407/article/details/126129254