中信国际inMotion项目Session id重复问题排查计划
中信国际inMotion项目Session id重复问题排查计划
问题描述
在2025年1月26日21:03分收到inMotion生产问题邮件反馈,客户A 在2025年1月24日17:39分登录inMotion后,在使用过程中,账户等信息突然显示成另一个客户B的信息,而客户A自己的账户信息则丢失不见,影响到客户A的正常使用和客户B的信息泄露。
初步分析
问题出现后,通过生产日志排查,确认以下几个关键信息:
- 生产上出现
X-Session-ID会话数据覆盖问题,导致已经登录的用户的信息被后面登录的用户的信息信息覆盖,从而导致生产反馈的问题。 - 通过日志记录确认,相同
X-Session-ID在不同用户的操作日志中,最先都是发生在用户调用登录接口时就已经发生,并非在用户登录后的业务操作中突然发生重复。客户A反馈的问题发生在客户B登录成功之后。 - 登录请求输出的日志中,最早出现
X-Session-ID的日志是Spring Session Framework相关机制记录的,即由Spring Session Framework产生的会话ID就已经发生重复。 - 会话在
25年1月后出现了重复的问题(当前可查数据),问题发生前是否存在相应的代码更新和类似的问题出现需要进一步调查。 - 业务代码上使用了:
Spring-session-data-redis机制进行会话管理以及会话共享。 - 基于25年1月24日出现的case,发现出现会话重复的用户是在不同的运行节点上的不同时间内进行的登录操作,而且是两个VM在较短的时间(差距在16分钟)差内产生了重复的随机数,看起来所产生的随机数有一定的可预测性,类似伪随机数的效果。
- 已知生产上进行过节点扩容,且是通过镜像操作系统的方式操作的,由
AB 扩容出ABB1B2四个vm。
排查方向
结合已知的关键信息:会话ID重复和会话由Spring Session Framework管理。初步梳理出以下排查方向:
-
重复的会话ID来源问题
-
是否可能是由登录请求携带过来的?
-
是否可能是意外复用了上一个用户的请求信息?
tomcat进行性能优化,底层对大量的对象(如:
Request)进行了复用避免频繁创建对象,而且通过recycle机制回收对象。而Spring Session Framework将当前的session信息委托到tomcat提供的request对象的attribute中暂存
-
-
开源版本问题:是否可能是由于当前使用的
Spring-session-data-redis依赖版本导致的bug? -
随机数熵源问题:
java.util.UUID使用的SecureRandom使用的熵源是否会返回重复概率较高的随机数(推测来源于初步分析6,随机数的可预测性)?- 镜像导致的随机种子文件没有重置?
/var/lib/systemd/random-seed - jvm使用的熵源是/dev/urandom?
- 镜像导致的随机种子文件没有重置?
-
应用代码问题:
- 应用代码上是否使用了特殊的
uuid生成机制覆盖了默认的基于JDKjava.util.UUID的id生成方式? - 应用代码中某些地方对session进行了意外操作导致的连锁反应?
- 应用代码上是否使用了特殊的
-
JDK版本问题:当前使用的
JDK版本的UUID实现方式是否有已知问题导致较高的重复概率? -
其它。统计生产发生重复的日志,分析是否存在共性。
综上,需要重点排查Spring Session Framework的会话管理机制、熵源、应用代码等方面,找出可能存在的问题点。
排查方法
-
重复的会话ID来源问题
- 接收方:通过日志、防火墙抓包等方式确认接收方收到的请求中是否包含
X-Session-ID - 发送方:通过检查发送方的日志等方式确认发送方收到的请求中是否包含
X-Session-ID - 查找对应版本的tomcat相关代码和公开资料,确认recycle机制是否存在请求信息泄露问题。
- 查找对应版本的
Spring-Session相关代码和公开资料,确认会话管理机制是否存在请求信息泄露问题。
- 接收方:通过日志、防火墙抓包等方式确认接收方收到的请求中是否包含
-
开源版本问题
-
随机数熵源问题
-
统计冲突节点: 已知进行过VM镜像扩容,应统计生产样本确认发生会话冲突的节点的情况,是否存在规律。
假设A B为已有节点,AA BB为扩容节点,统计是否出现以下规律:冲突只发生在已有节点与扩容节点之间,即A和AA,B和BB。如果存在明显规律,则可以佐证随机数熵源可能存在问题。
-
确认各case的时间差值: 统计当前可查的case的时间差值,确认2025.1.24的case所表现出来的可预测性是否具备普遍性。
-
检查熵池状态:Linux 系统熵不足(
/proc/sys/kernel/random/entropy_avail 值低)可能导致SecureRandom 生成的随机数质量下降。高压力生成随机数的情况下检查熵池的状态。# 检查 SecureRandom 使用的熵源(Java 配置) grep securerandom.source $JAVA_HOME/jre/lib/security/java.security |grep securerandom # 加压 dd if=/dev/random of=/dev/null bs=1M count=100000 cat /proc/sys/kernel/random/entropy_avail -
镜像克隆导致随机种子重复: 克隆的虚拟机可能共享相同的随机种子文件(如
/var/lib/systemd/random-seed)或machine-id# 检查随机种子文件是否重复(对比不同虚拟机) md5sum /var/lib/systemd/random-seed # 检查 machine-id 是否唯一 cat /etc/machine-id -
程序模拟验证压力环境下UUID重复: 通过java程序模拟多线程高并发环境下多节点UUID是否可能出现重复。
-
验证 SecureRandom 的实际行为: 检查当前
SecureRandom 使用的算法和提供者。import java.security.SecureRandom; public class CheckSecureRandom { public static void main(String[] args) { SecureRandom sr = new SecureRandom(); System.out.println("Algorithm: " + sr.getAlgorithm()); System.out.println("Provider: " + sr.getProvider().getName()); } }
-
-
应用代码问题
- 检查应用代码
-
JDK版本问题
- 查阅JDK Bug Database,确认是否存在已知问题
时间计划
| 排查内容 | 开始日期 | 结束日期 |
|---|---|---|
| 重复的会话ID来源问题 | 2025/2/27 | 2025/2/28 |
| 开源版本问题 | 2025/2/27 | 2025/2/28 |
| JDK版本问题 | 2025/2/27 | 2025/2/27 |
| 应用代码问题 | 2025/2/27 | 2025/2/28 |
| 随机数熵源问题-统计冲突节点规律、确认各case的时间差值是否具备普遍性、验证 SecureRandom 的实际行为 | 2025/3/3 | 2025/3/5 |
| 随机数熵源问题-UUID压力验证 | 2025/3/3 | 2025/3/4 |
排查过程
重复的会话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的登录请求日志(未执行)
应用代码中通过CommonsRequestResponseLoggingFilter拦截器对请求数据进行了记录,通过查询客服B的登录请求日志,找到该拦截器的输出的headers部分:
应用代码问题
-
确认应用使用的是
Spring-Session-Data-Redis模块提供的默认uuid生成方式 -
确认应用对
session的attribute的写操作都属于业务上的key,没有覆盖内部数据
-
确认应用层代码调整的关于session的默认行为
-
通过
HeaderHttpSessionIdResolver将sessionId的key修改为X-Session-ID,不会导致sessionId重复。 -
该配置未使用
-
开源版本问题
应用基于SpringBoot体系,使用了嵌入式的tomcat以及Spring-Session-Data-Redis模块。
排查1. 确认Tomcat的recycle机制
生产上使用的tomcat版本为9.0.38。
tomcat中有不少需要高频使用的对象采用了回收复用的方式减少对象垃圾回收以及创建的开销,如Processor。需要确认相关机制是否存在遗留历史数据的问题。
processor是tomcat的网络层接收到请求后最开始的入口,网络请求准备好后,从recycledProcessors中获取一个可复用的处理器进行处理。
这里总结主要过程,从缓存的processor中尝试获取复用对象,获取不到则新建,执行请求后,通过调用release方法回收对象,回收时先调用processor对象的recycle方法释放其内部资源,然后缓存该对象。
从这个流程可以发现:
- 一个
processor对象同一时刻只可能处理一个请求,也即说明不存在多线程问题 -
processor的recycle方法在放回缓存前被调用,也即说明如果recycle方法失败异常,该对象不会被复用
所以,如果recycle方法实现没问题,那么tomcat的复用机制是可信的。
接下来,分析service方法开始 processor内部
这里主要关注request这种成员变量都有哪些,这些变量都是需要复用的,需要保证无状态或正确回收。
经分析发现,
以下为无状态变量:adapter httpParser
以下为合理重置状态的变量:asyncStateMachine inputBuffer outputBuffer request response sendfileData socketWrapper sslSupport upgradeToken
以下为每次请求会自动设置的变量:contentDelimitation http09 http11 pluggableFilterIndex keepAlive openSocket readComplete userDataHelper
以下为只读变量:protocol
没有发现未合理处理的变量
processor最终将请求委托给CoyoteAdapter处理。
其中,与Session相关最重要的变量是request,里面保存了当前请求的数据,包括请求头等。
request中主要关注headers 、attributes session requestedSessionId变量,headers保留了用户发送的header信息,attributes保存了请求处理时产生的各种属性,session则表示会话对象,requestedSessionId表示用户传递的cookie中的会话id,通过该id优先查找符合的会话。SpringBoot环境下,有独立的session管理机制
其中,用户请求的header头交给原始的org.apache.coyote.Request管理。该`org.apache.coyote.Request`的回收由Http11Processor触发
确认header被正确回收。
org.apache.catalina.connector.Request对象,一般在CoyoteAdapter处理完成请求后触发,同时在没有正确回收时,由Http11Processor回收时触发检查并回收。
触发检查。
触发日志并回收
因此,可以认为其它三个对象也被正确回收
排查1结论:tomcat相关的复用对象未发现可疑的泄漏点。
排查2. 确认spring-session-data-redis模块的session管理机制
进一步通过分析spring-session-data-redis模块的代码进行二次检查。
在spring-session-data-redis模块中,首先通过SessionRepositoryFilter拦截HTTP请求,这是session处理的入口。
这里的关键点是SessionRepositoryRequestWrapper对象,通过该对象实现将对session的操作转交给Spring Session。
SessionRepositoryRequestWrapper是一个实现了javax.servlet.ServletRequest接口的包装对象,这里主要重写了Session操作相关的方法,其中关键是getSession。
这里是Spring Session获取或创建Session的核心逻辑,可以看到整个过程主要分为3个步骤:获取已经存在的Session -> 获取用户请求中指定的Session -> 创建新的Session。
接下来,我们分析这3个步骤是否存在错误复用Session的可能性。
首先,明确知道SessionRepositoryRequestWrapper对象是每次请求都新建的,不存在多线程并发问题。
那么,检查getCurrentSession,该方法尝试获取保存在Request的attributes中的session对象
而此时的Request是来源于Tomcat的,而之前的分析中,已经确认Tomcat提供的Request对象没有误用的问题,那说明此时应该是返回null的。
接着,检查getRequestdSession,该方法尝试根据用户传递的sessionId获取session对象
在这里先通过sessionId解析器获取Request中的sessionId,在应用配置上,我们确认当前使用的是HeaderHttpSessionIdResolver,且headerName为X-Session-ID。结合Tomcat和请求报文的实际情况,确定应该无法获取到sessionId,说明该步骤也无法获取到session。
最后,分析createSession方法,结合已经排查到的线索,基本上已经将怀疑的方向收缩到session创建上了。
可以明显看到整个过程只是通过UUID生成sessionId后同步到redis就结束了,并没有获取其它session的操作。
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实现
这里可以明确使用的是SecureRandom作为随机数生成器。
确认实际使用的PRNG提供者
总结
20250228
结合当前排查的线索综合分析,tomcat以及Spring Session机制并没有发现问题,确认请求中不包含X-Session-ID后,重大的怀疑线索只剩下UUID了。