Spring Session集成Redis后Principal丢失问题求助
这种偶发的会话异常确实挺闹心的——明明还能访问受限页面,但关键的Principal却为空,直接影响业务逻辑。结合Spring Session + Redis的常见坑,给你几个排查和解决的方向:
1. 会话序列化/反序列化异常
Spring Session默认的JDK序列化方式有时候会踩坑:如果你的用户类(或者Principal关联的对象)没正确实现Serializable接口,或者序列化过程中某些属性被遗漏,就会导致Redis中存储的会话数据反序列化后Principal为空,但Spring Security的权限缓存还没失效,所以你还能访问受限页面。
解决方案:换成JSON序列化方式,更稳定也更易调试。你可以在配置类里自定义序列化器:
@Configuration public class RedisSessionConfig { @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); return serializer; } }
或者直接用配置文件简化配置:
spring.session.data.redis.serializer.type=json
2. 会话过期时间不一致
如果Redis中存储的会话键过期时间,和Spring Boot配置的会话超时时间不匹配,就可能出现:Redis里的会话已经被清理,但Spring Security的权限缓存还没过期,导致你能访问页面,但会话里的Principal已经没了。
排查步骤:
- 先确认你的会话超时配置:
server.servlet.session.timeout=3600s # 比如1小时
- 用Redis客户端执行
KEYS spring:session:*找到会话键,再用TTL 键名查看过期时间,确保和配置的超时时间一致。 - 如果不一致,检查是否设置了Redis的默认过期策略,或者Spring Session的命名空间是否正确:
spring.session.data.redis.namespace=spring:session
3. 并发请求的竞态条件
当多个请求同时操作会话时,可能出现竞态问题:比如一个请求在刷新会话数据,另一个请求同时读取,导致读到未完全更新的会话,Principal就成了null。
解决方案:
- 调整Spring Session的Redis刷新模式为
IMMEDIATE,确保会话修改后立即写入Redis:
spring.session.data.redis.flush-mode=immediate
不过这个模式会增加Redis的请求量,需要权衡性能;
- 也可以尝试开启Redis事务支持,减少并发冲突的概率。
4. Spring Security与Session的上下文同步问题
如果你的系统里有异步方法,或者SecurityContext的传递有问题,就可能出现:SecurityContextHolder里的权限信息还在,但Session中的Principal已经丢失,导致能访问页面但获取不到用户信息。
解决方案:
- 如果有异步方法,确保异步线程能正确继承SecurityContext:
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(runnable -> { // 保存当前线程的SecurityContext SecurityContext context = SecurityContextHolder.getContext(); return () -> { try { // 给异步线程设置上下文 SecurityContextHolder.setContext(context); runnable.run(); } finally { SecurityContextHolder.clearContext(); } }; }); executor.initialize(); return executor; } }
- 检查Spring Security的会话创建策略,确保是基于Session的:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); } }
5. Redis连接或网络波动
偶尔的Redis连接超时、网络抖动,可能导致会话数据没有正确写入或读取。当读取会话失败时,Spring Session可能会创建一个空的新会话,但Spring Security的权限缓存还没失效,就会出现你遇到的情况。
排查步骤:
- 查看Redis的日志,是否有连接超时、读写错误的记录;
- 调整Redis连接池的配置,增加超时时间和重试机制:
spring.redis.timeout=5000ms spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.max-idle=8 spring.redis.lettuce.pool.min-idle=0 spring.redis.lettuce.pool.max-wait=-1ms
建议你先从序列化问题和会话过期时间入手排查,这两个是最常见的原因。如果还是没解决,可以尝试在出现问题时,用Redis客户端直接查看会话数据,看看里面的Principal是否真的为空,这样能更快定位问题。
内容的提问来源于stack exchange,提问作者ROZZ




