中信国际inMotion项目Session id重复问题阶段性排查进度总结_20250304

中信国际inMotion项目Session id重复问题阶段性排查进度总结_20250304

  1. 分析应用层代码,检查应用层日志和网关相关日志。我们发现“第二个登录的用户”的请求报文没有传递x-session-id​,这意味着该用户的session-id​是在进入微服务后生成的。因此,可以基本排除浏览器跨session​攻击的可能性。
  2. 分析框架层代码,特别是SpringBoot和Tomcat的Session处理相关源码,未发现异常。
  3. 针对发生的问题,我们在开源社区(SpringBoot、Tomcat、JDK)中进行了分析、搜索和研究,但暂未找到类似的问题和相关的升级补丁。

详细排查过程

重复的会话ID来源问题

sessionid的生成日志记录归集在ELK中,经过分析:

  • 客户A于2025-01-24 17:39:37登录成功,并生成了sessionid:33af5f90-a7d2-47cc-bfcb e2d2911e5d38

  • 客户B于2025-01-24 17:55:22 登录成功,并生成 sessionid:33af5f90-a7d2-47cc-bfcb e2d2911e5d38

排查1. 查询客户B的登录请求日志

分析客户B的日志以及网关的日志,收到的请求header中没有找到包含X-Session-ID​的头,因此说明因此不是来自请求方的数据导致获取到非法的session。而是由系统内部生成。

网关日志:

应用日志

开源版本问题排查

应用基于SpringBoot​体系,使用了嵌入式的tomcat​以及Spring-Session-Data-Redis​模块。

排查1. 开源社区issues

生产上使用的springboot​版本为2.2.4.RELEASE​。

  1. github​相关模块的issue​上没有找到类似的问题报告。参考链接:https://github.com/spring-projects/spring-session/issues
  2. stackoverflow​上没有找到类似的问题报告。参考链接:Posts matching 'spring sessionid duplicate' - Stack Overflow

生产上使用的tomcat版本为9.0.38​。

  1. 在apache tomcat的bug database中没有找到该版本类似的问题报告。参考链接:https://bz.apache.org/bugzilla/buglist.cgi?bug_status=all&content=request%20attributes%20leak&no_redirect=1&order=Importance&product=Tomcat%209&query_format=specific

排查2. Tomcat的请求过程源码分析

生产上使用的tomcat版本为9.0.38​。

tomcat中有不少需要高频使用的对象采用了回收复用的方式减少对象垃圾回收以及创建的开销,如Processor​。需要确认相关机制是否存在遗留历史数据的问题。

image

processor是tomcat的网络层接收到请求后最开始的入口,网络请求准备好后,从recycledProcessors​中获取一个可复用的处理器进行处理。

image

执行处理

image

回收资源,重复利用处理器。

image

这里总结主要过程,从缓存的processor​中尝试获取复用对象,获取不到则新建,执行请求后,通过调用release方法回收对象,回收时先调用processor​对象的recycle​方法释放其内部资源,然后缓存该对象。

从这个流程可以发现:

  1. 一个processor​对象同一时刻只可能处理一个请求,也即说明不存在多线程问题
  2. processor​的recycle​方法在放回缓存前被调用,也即说明如果recycle​方法失败异常,该对象不会被复用

所以,如果recycle​方法实现没问题,那么tomcat的复用机制是可信的。

接下来,从processor的service​方法开始分析 processor​内部

Snipaste_2025-03-04_02-11-16

这里主要关注request​这种成员变量都有哪些,这些变量都是需要复用的,需要保证无状态或正确回收。

image

经分析发现,

以下为无状态变量:adapterhttpParser

以下为合理重置状态的变量:asyncStateMachineinputBufferoutputBufferrequestresponsesendfileDatasocketWrappersslSupportupgradeToken

以下为每次请求会自动设置的变量:contentDelimitationhttp09http11pluggableFilterIndexkeepAliveopenSocketreadCompleteuserDataHelper

以下为只读变量:protocol

没有发现未合理处理的变量

processor最终将请求委托给CoyoteAdapter​处理。

image

其中,与Session相关最重要的变量是request​,里面保存了当前请求的数据,包括请求头等。

image

request​中主要关注headers​ 、attributessessionrequestedSessionId​变量,headers​保留了用户发送的header​信息,attributes​保存了请求处理时产生的各种属性,session​则表示会话对象,requestedSessionId​表示用户传递的cookie中的会话id,通过该id优先查找符合的会话。SpringBoot环境下,有独立的session管理机制

image

其中,用户请求的header头交给原始的org.apache.coyote.Request​管理。该`org.apache.coyote.Request`​的回收由Http11Processor​触发

image

image

image

image

因此,确认header​被正确回收。

org.apache.catalina.connector.Request​对象,一般在CoyoteAdapter​处理完成请求后触发,同时在没有正确回收时,由Http11Processor​回收时触发检查并回收。

Snipaste_2025-03-04_02-49-17

触发检查。

image

触发日志并回收

Snipaste_2025-03-04_02-50-10

Snipaste_2025-03-04_02-51-08

因此,可以认为其它三个对象也被正确回收

Snipaste_2025-03-04_02-36-17

image

排查2结论:tomcat相关的复用对象未发现可疑的泄漏点。

排查3. Spring Session会话管理机制源码分析

进一步通过分析spring-session-data-redis​模块的代码进行二次检查。

spring-session-data-redis​模块中,首先通过SessionRepositoryFilter​拦截HTTP​请求,这是spring session​处理的入口。

fedab727635d86f102b2d1a5d816c223

这里的关键点是SessionRepositoryRequestWrapper​对象,通过该对象实现将对session​的操作转交给Spring Session​。

ad22f9d81a0eb19b15640f7abb614ffe

SessionRepositoryRequestWrapper​是一个实现了javax.servlet.ServletRequest​接口的包装对象,这里主要重写了Session​操作相关的方法,其中关键是getSession​。

cdabbb5b9e8ba66639b79ed9cbc2d06f

31fd184cae5f216f93219fb62b43099e

这里是Spring Session​获取或创建Session​的核心逻辑,可以看到整个过程主要分为3个步骤:获取已经存在的Session -> 获取用户请求中指定的Session -> 创建新的Session。

接下来,我们分析这3个步骤是否存在错误复用Session​的可能性。

首先,明确知道SessionRepositoryRequestWrapper​对象是每次请求都新建的,不存在多线程并发问题。

那么,检查getCurrentSession​,该方法尝试获取保存在Request​的attributes​中的session​对象

image

而此时的Request​是来源于Tomcat​的,而之前的分析中,已经确认Tomcat​提供的Request​对象没有误用的问题,那说明此时应该是返回null​的。

接着,检查getRequestdSession​,该方法尝试根据用户传递的sessionId​获取session​对象

image

image

在这里先通过sessionId解析器获取Request​中的sessionId,在应用配置上,我们确认当前使用的是HeaderHttpSessionIdResolver​,且headerName​为X-Session-ID​。结合Tomcat​和请求报文​的实际情况,确定应该无法获取到sessionId,说明该步骤也无法获取到session​。

最后,分析createSession​方法,结合已经排查到的线索,基本上已经将怀疑的方向收缩到session创建​上了。

image

image

image

可以明显看到整个过程只是通过UUID​生成sessionId后同步到redis就结束了,并没有获取其它session​的操作。一般可以认为基于随机数生成的UUID​是有重复概率的。

排查3.1 分析Spring Session​的入口拦截器SessionRepositoryFilter

在找到spring seesion​的核心入口后,以Spring Session​的入口拦截器SessionRepositoryFilter​为分割点,将可能性分为两部分:

  1. 对象来自tomcat提供的Request​对象,由于Spring Session​将自己生成的Session​对象存放到Request​对象的attribute​中,如果Request​对象没有正确清理,那可能返回错误的session
  2. 对象来自CommonsReqquestResponseLoggingFilter​和SessionRepositoryFilter​这两个拦截器中间的某个拦截器机制,由于拦截器允许通过包装的方式对request​对象进行多层包装,每一层包装都可能返回错误的session

结合之前对tomcat机制的分析,以及日志中输出的其它请求信息都是正确的,基本上可以确认Request​对象是已经正确清理了的,所以先将1​的可能性调低,重点检查2

通过debug以及代码检查,将两个过滤器中间的其它过滤器都列出来,其中后面标记1的过滤器是有包装request​对象的,需要深入分析其实现。

排查3结论:Spring Session的入口拦截器较为复杂,需要进一步对齐进行拆解分析,也是下一步较为重要的分析点

应用代码问题

  1. 确认应用使用的是Spring-Session-Data-Redis​模块提供的默认uuid生成方式

    具体位置位于:SessionRepositoryFilter​中,详情见:确认spring-session-data-redis模块的session管理机制​部分。

  2. 确认应用对session​的attribute的写操作都属于业务上的key,没有覆盖内部数据

    6cc3e7b92bbeab3ce6c70f1eeeb37bbe

  3. 确认应用层代码调整的关于session的默认行为

    1. 基于行业通用做法,通过HeaderHttpSessionIdResolver​将sessionId的key修改为X-Session-ID​,不会导致sessionId重复。

      image

    2. 该配置未激活使用

      82e5db3442dd480bc0f1835021c12429

JDK版本问题排查

生产上使用的JDK版本为openjdk 1.8​版本。

JDK BUG SYSTEM​中没有找到类似的问题描述。参考链接:[JDK-4917896] (spec) UUID.compareTo() spec unclear on relationship between UUID ordering and "greater" - Java Bug System

先确认UUID​的版本,根据生产上日志记录的sessionId​,确认版本

System.out.println(UUID.fromString("03af5f90-a7d2-47cc-bfcb-e2d2911e5d38").version());

输出:

4

确认生产使用的UUID​版本为4​,即随机数实现(pseudo randomly generated)。

接下来,尝试分析对应版本的UUID​实现

image

这里可以明确使用的是SecureRandom作为随机数生成器。

image

image

确认实际使用的PRNG​提供者。linux下默认为:NativePRNG​。参考链接:https://docs.oracle.com/en/java/javase/11/security/oracle-providers.html#GUID-3A80CC46-91E1-4E47-AC51-CB7B782CEA7D

下一步工作

  1. 整体梳理登录及session​产生流程,提供序列图;
  2. 深入分析 Spring-session 框架下底层多个涉及 session 的 filter 源码;
  3. 获取服务运行时的内存库快照,分析 Session id 在内存中的存放,从而反推代码中对 Session 的操作。
  4. 根据 #2 点的分析情况(如认为有线索),通过压测验证 UUID 重复的概率。