14 spring-security 中的 SessionManagerFilter 导致的每刷新一次页面 JSESSIONID 切换一次 导致 session 不可用
前言
这个问题是最近的一个老项目 碰到的一个问题
假设 我现在在同一个页面, 刷新一下页面, 加载完毕之后, 再刷新一下页面
然后 可以看到的是两次刷新页面所发送的请求, 携带的 JSESSIONID 不一样, 然后 导致了 服务端这边使用 session 相关特性失效
然后 这里我们就是来看一下 处理的原因
首先项目是引入了 spring-security, 然后 进而导致的这个问题
情况如下, 以红线作为 第一次 和 第二次 相关网络请求的分隔符
然后可以看到的是 第一批次的请求中携带的 JSESSIONID 均为 “4415237FB3237945BB3103BCB187F5F3”
然后 响应了一个 set-cookie 的响应头, 告诉客户端这边重新更新 cookie, 第一批次的请求的所有响应里面都有这个 set-cookie, 并且每一个 set-cookie 中的 JSESSIONID 不一

第二个批次的请求如下, 可以看到携带的 JSESSIONID 为 “9142F6629C4D076207C283CD8A103BC9”
然后 客户端这边挑选的是 上面的 ”/serviceStatsByLv2” 的 JSESSIONID 进行设置, 当然 这个挑选是由浏览器这边来控制的, 我们暂时不关心这个问题
然后第二批次的这批请求, 基本上都有 set-cookie 的响应头

按照 我们的通常的 web 经验的理解, 一个 session 应该是有一定的生命周期的, 为啥这里每一批次请求 就会切换 JSESSIONID 呢??
这是和 spring-security 中的一个 SessionManagementFilter 有关系
问题的调试
为了简化问题, 我们这里将 tomcat 的请求处理线程设置为 1
设置方式为 配置 “server.tomcat.max-threads” 为 1
然后 我们来看一下 这里的情况, 假设这里客户端 一次性发送了 8 个请求, 因为我们这里将 tomcat 的请求处理线程设置为了 1, 因此 这里的 8 个请求是依次处理的
假设这 8个请求携带的 JSESSIONID 为 $sessionOld
然后 第一个请求处理如下, 可以获取到 session, 并且在 SessionManager 中存在, 然后执行 ChangeSessionIdAuthenticationStrategy.applySessionFixation 进而调用 Request.changeSessionId 来实现 session 的更新, 这个更新操作会在 SessionManager 中移除 $sessionOld, 然后创建 $sessionNew

然后 第二个请求 到 第八个请求, 他们携带的是 JSESSIONID 为 $sessionOld
到这里 request.getSession(false) 的地方从 SessionManager 中获取 session, 是获取不到的
然后这里直接走了 return 的处理
然后 这个处理之后的其他业务使用到了 request.getSession(true) 的相关业务操作, 会创建新的 session

假设 tomcat 的工作线程远大于请求的数量的情况下会怎么样?
假设 tomcat 的工作线程数量远大于 这里的请求数量8
可以大概的理解为 这8个请求会同时执行
情况就变得比较复杂了, 有很大的不确定性, 但是 规则如下
并发第一批到达 SessionManagementFilter 的地方的这批线程, 此时 session 均是有效的, 然后都会调用 ChangeSessionIdAuthenticationStrategy.applySessionFixation
然后 第一批次之后的其他请求, 就和上面一样, 直接 return 了, 后面的 request.getSession(true) 的处理会创建新的 session
然后新的 session 的 JSESSIONID 的信息会通过 set-cookie 响应头 响应给客户端
切换了 sessionId 之后数据还在吗?
更新的处理分为两个 part, 这里是 SessionManager 的部分
可以看出的是仅仅是更新了 session 的 id, 然后 发送了通知到各个 sessionListeners, containerListeners

Request 这边的处理如下, 就是更新了 Request 的 requestedSessionId, 然后增加了 set-cookie 响应给客户端
这里整个过程 session 还是使用的原来的 session, 只是说 id 切换了

引发的问题是什么?
总结一下 默认情况下这个 SessionmanagerFilter 的作用如下
如果请求到了这里, 这个 request 关联的 session 有效, 就会切换 session
引发的问题是 我们分为如下几种情况讨论
- 假设客户端这边是串行发送请求给服务端, 这个是没有问题的, 第一个请求携带 $sessionIdOld, 然后响应 $sessionIdNew, 然后第二个请求携带 $sessionIdNew, 响应 $sessionIdNew2, 如此往复, 该 session 始终有效, 但是每次都在切换 sessionId, 这可能就是设计者最初的设计目的
- 假设客户端这边是并行发送 N 个请求到服务端, 服务器这边串行处理, 第一个请求会更新 $sessionId, 然后之后的请求如果使用到了 session 的话, 会创建新的 Sesssion, 然后服务器这边响应了多个 JSESSIONID 给客户端, 客户端只会接受一个, 服务器这边还保存了 这N个session, 会造成这里的 (N-1) 个 session 的泄露, 无端占用服务器的资源, 并且 session 整体功能不可用
- 假设客户端这边是并行发送 N 个请求到服务端, 服务器这边并行处理, 第一批次请求会更新 $sessionId, 然后之后批次的请求如果使用到了 session 的话, 会创建新的 Sesssion, 然后服务器这边响应了多个 JSESSIONID 给客户端, 客户端只会接受一个, 服务器这边还保存了 这N个session, 会造成这里的 (N-1) 个 session 的泄露, 无端占用服务器的资源, 并且 session 整体功能不可用
整体来说, 在大多数的情况下面, 这个功能都会影响到 session 的功能
怎么处理?
SpringSecurity 的这一部分 Filter 的初始化是在 这里初始化的
这里的 sessionManagement() 的过程职中往 http.configurers 中放入了一个 SessionManagementConfigurer, 这个就是 后面 build SessionManagement 的 Configurer
可以再 HttpSecurity 的配置的过程中去调整, 操作 api 如下 HttpSecurity.removeConfigurer

也可以在 SessionManagementConfigurer 中去操作, getSessionAuthenticationStrategy 如下
可以操作的地方有 providedSessionAuthenticationStrategy, sessionFixationAuthenticationStrategy, DEFAULT_SESSION_FIXATION_STRATEGY 等等地方
另外还有 postProcess 进行处理

最后就只有在 SessionManagementFilter 中操作了

呵呵 我这里采用最简单且粗暴的处理方式, 直接反射更新 SessionManagementFilter 中的 sessionAuthenticationStrategy
@Component
public class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
public void init(WebSecurity web) throws Exception {
web.postBuildAction(() -> {
try {
Field securityFilterChainBuildersField = web.getClass().getDeclaredField("securityFilterChainBuilders");
securityFilterChainBuildersField.setAccessible(true);
List securityFilterChainBuilders = (List) securityFilterChainBuildersField.get(web);
if (!securityFilterChainBuilders.isEmpty()) {
HttpSecurity httpSecurity = (HttpSecurity) securityFilterChainBuilders.get(0);
SessionAuthenticationStrategy sessionAuthenticationStrategy = httpSecurity.getSharedObject(SessionAuthenticationStrategy.class);
if (sessionAuthenticationStrategy != null && (sessionAuthenticationStrategy instanceof CompositeSessionAuthenticationStrategy)) {
Field delegateStrategiesField = CompositeSessionAuthenticationStrategy.class.getDeclaredField("delegateStrategies");
delegateStrategiesField.setAccessible(true);
List delegateList = (List) ReflectionUtils.getField(delegateStrategiesField, sessionAuthenticationStrategy);
delegateList.clear();
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
完
更多推荐
所有评论(0)