Spring Security 4.1.0 定期从安全上下文获取空用户实例问题咨询
问题原因分析与排查方案
核心原因(按出现概率排序)
- Session反序列化异常
Spring Security默认会把完整的Authentication对象存储在用户Session中,当服务器重启、Session持久化/钝化激活时,会触发序列化反序列化逻辑。如果你的ContextUser类或它继承的父类User没有正确实现Serializable接口,或者id、login等业务字段被transient修饰,又或者自定义的序列化/反序列化方法存在逻辑错误,就会出现反序列化后生成属性为默认值的空实例,而Authentication的isAuthenticated状态、权限列表是独立存储的字段,反序列化正常,因此这两个属性保持正确。该问题偶发的特征符合Session过期重新激活、服务器节点重启的触发规律。 - 异步场景上下文错乱
你在安全配置中开启了@EnableAsync异步注解,Spring Security默认使用ThreadLocal存储安全上下文,仅对当前请求线程有效。如果你的test接口内部调用了异步方法,或者web容器的线程池出现了线程复用、上下文未清空的情况,就可能拿到异步线程遗留的未初始化的ContextUser实例,或者上下文传递不完整的实例,该问题周期性出现的特征符合线程池复用的规律。 - 会话迁移并发冲突
你配置了sessionFixation().migrateSession()策略,用户登录后会生成新的Session并将旧Session的属性全量复制到新Session中。如果复制过程中刚好有请求读取安全上下文,就会出现认证状态、权限列表已复制完成,但ContextUser的自定义属性还未完成复制的情况,最终你拿到的就是属性为空的实例。 - 业务代码初始化异常
检查ContextUser的带参构造方法是否存在逻辑漏洞,比如传入User参数为空时没有抛出异常而是直接生成了空实例;同时检查TestUserService.findByLogin方法是否在缓存击穿、并发查询的场景下会返回属性为空的User对象,最终导致生成的ContextUser属性为空。
排查步骤
- 给
ContextUser和父类User都实现Serializable接口,确认所有业务字段没有被transient修饰,编写单元测试手动触发序列化、反序列化,验证反序列化后的实例属性是否正常。 - 如果项目存在异步调用逻辑,添加配置指定安全上下文的传递策略:
// 启动类中添加 static { System.setProperty("spring.security.strategy", "MODE_INHERITABLETHREADLOCAL"); }
- 在
TestUserDetailsService.loadUserByUsername方法和Controller获取用户的代码处分别添加日志,打印返回的ContextUser的id和login值。如果Controller拿到空实例时loadUserByUsername没有被调用,就可以确定是Session反序列化相关的问题;如果被调用了,就需要排查用户查询的业务代码。 - 检查项目中是否有自定义的Filter、AuthenticationProvider修改了Authentication的Principal属性,是否存在错误替换为空
ContextUser的逻辑。
内容的提问来源于stack exchange,提问作者Stack30003
相关产品推荐
相关产品推荐

