其实,原生的API是不行的。(不过文章末尾我在通读源码之后用反射写了一个Session共享的方式,需要的可以直接使用)
先大概了解一下为了创建Session会话域,原生API做了什么
ServerCookie
对Request进行了赋值(Request类是HttpServletRequest的实现类)Session和jsessionid
属性进行了绑定Session和jsessionid
属性(主要是这两个)在很多地方都起着互相校验的功能(修改时需要两个一起修改,否则会导致request.getSession()时重新生成Session)request -》context -》manager
的findSession
方法直接获取Session后传给Request对象当形参下面进行论述
那么正常情况下我们切换浏览器(切换Session)是不能够获取这个k-v对的,但是我们可以通过在浏览器、PostMan、js代码
将JSESSIONID设置为刚才的
这样就能在服务器中劫持Session了但是实际开发环境中,这种方案可行度接近于0,那么尝试下后端原生API修改Cookie的值??
Cookie[] cookies = request.getCookies();
if(cookies!=null)
for(Cookie c: cookies){
//此案例中Cookie仅包含JSESIONID,等价于(request.getSession().getId())
System.out.println(c.getName()+":"+c.getValue());
}
request.getCookies()[0].setValue("newJSessionId");
//还是之前的JSESSIONID
System.out.println(request.getSession().getId());
比如在使用
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修改后却没有用?
一直跟进request.getSession()
方法,发现修改Cookie中JSESSIONID之前和之后都是在这个地方返回的session对象,证明在getSession()之前,session一定被初始化了,修改Cookie不影响直接获取session
整个过程没有看到任何跟Cookie中JSESSIONID有关的代码
因此可以大胆推测:session对象早在请求打过来的时候就获取了其对应的地址值,后期修改Cookie不会导致session对象指向的地址改变
既然session不能被中途修改,那么一定是在初始化的时候,从Cookie中取出JSESSIONID,然后赋值给的session
一个小技巧,可以在session对象上面打一个断点,每次(按F9)执行到修改session值的时候都会进入
request.getSession()
;然后新开一个浏览器请求两次,模拟创建Session和寻找Session的过程懒汉式
的,如果你不调getSession方法,他就不会去创建,更不会在响应Cookie里面携带JSESSIONID。释放环节,为下一次请求做准备
浏览器刷新一下请求
这次不需要创建,而是直接根据requestedSesionId来寻找,然后将其维护在request的session属性中
再次释放资源
Session的创建,寻找都是在doGetSession(boolean create)
方法中完成的。
由上述可知,getSession()最终调用的逻辑一定是doGetSession(),验证一下
request
这里明显有个create,决定这个方法是获取还是创建
在接口中写下代码模拟调用两次getSession()。现在仅模拟第二次请求(不需要再去创建Session,只需要去寻找Session)
HttpSession session1 = request.getSession();
HttpSession sessio2n = request.getSession();
通过debug可以知道
通过上面的案例可以侧面反映出大标题4中为什么最后需要进行资源释放
每次请求
都不需要getSession(),那么Session永不创建,Cookie中的JSESSIONID永不创建一次请求
中多次使用getSession(),那么只有第一次会涉及createSession、findSession、request.session获取接下来
每一次都只直接request.session获取早期应该是可以通过url上指定jsessionid来设置的,现在会覆盖掉导致设置失效
CoyoteAdapter
类的后置初始化过程中,会将jsessionid写入request,至于你用不用session完全取决于自己。那么问题现在转成了这个requestSessionId是如何被初始化的
适配器模式的 Adapter接口的一个实现类
观察里面的方法,并在service
postParseRequest
的开头分别打上断点
其中service(核心业务)
先将Requset对象进行创建,
然后postParseRequest(后置处理)
将req写入request
这个方法名结合注释可知:解析完request之后必须要做的一些事。在开头打个断点然后发请求
在执行postParseRequest()之前,request对象中还没有值,所有的请求的值都存在于org.apache.coyote.Request req
中 执行完成之后就将请求参数赋值给了request
按这种url格式发送http://localhost:8088/pic/people/pic1;jsessionid=F0D3200C7D61C8D41E129924585B6234
这里通过解析request.getParam 用这个枚举类,即解析路径变量中的jessesionid 然后赋值给request
Cookie
赋值给request,而是直接从ServerCookie
中取值JSESSIONID塞给request
http://localhost:8088/pic/people/pic1;jsessionid=F0D3200C7D61C8D41E129924585B623这样的写法,会导致:
正是因为JSESSIONID是在这个CoyoteAdapter
类中的postParseRequest()
方法中的parseSessionCookiesId()
方法中就对JSESSIONID进行了赋值,导致如下情况:
很明显这个JSESSIONID是从ServerCookie中获取的,那二者有什么区别呢?
ServerCookie来自于coyoteRequest,即tomcat的底层实现,因此不能针对其Cookie进行修改从而达到模拟session的效果
因为request.getSession()
调用的是Reuqest类下的doGetSession()
,requestedSessionId
又是Request下的属性,那么能不能通过反射修改requestedSessionId的值来修改findSession()
的结果呢?答案是可以
HttpServletRequest
是一个接口,实际上传递是是其实现类RequestFacade
,在涉及到反射操作的时候需要再开辟一个RequestFacade
的栈空间指向HttpServletRequest
堆空间RequestFacade
实际上大部分调用的是Request
doGetSession(Boolean craete)
是最终获取Session对象的方法,包括初始化时session的生成、httpServletRequest.getSeesion()获取。区别是前者形参create=true、后者形参create=falsemanager.findSession(sessionId)
;当初始化时,先getRequestedSessionId()
根据JVM随机数获取sessionid,然后作为形参调用session = manager.createSession(sessionId);
因此可以有如下两条思路:
/**
* @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;
}
/**
* @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 extends RequestFacade> facadeClazz = requestFacade.getClass();
//RequestFacade获取Request
Field requestField = facadeClazz.getDeclaredField("request");
requestField.setAccessible(true);
//Request
Request request = (Request)requestField.get(requestFacade);
Class extends Request> 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);
}
经过异常处理,但不处理空指针异常(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 最后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 extends RequestFacade> 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 extends RequestFacade> facadeClazz = requestFacade.getClass();
Field requestField = facadeClazz.getDeclaredField("request");
requestField.setAccessible(true);
//Request
Request request = (Request) requestField.get(requestFacade);
Class extends Request> 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;
}
}