如何修复Spring Session中的"Session was invalidated"异常?
问题描述
我有一个/logout端点,通过调用HttpSession#invalidate()手动使会话失效,但偶尔会抛出以下异常(约每1000次请求出现十几次):
java.lang.IllegalStateException: Session was invalidated
at org.springframework.session.data.redis.RedisSessionRepository.save(RedisSessionRepository.java:129)
at org.springframework.session.data.redis.RedisSessionRepository.save(RedisSessionRepository.java:45)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:229)
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:145)
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)
2 lines skipped for [org.apache.catalina]
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
2 lines skipped for [org.apache.catalina]
异常核心是会话失效后仍被尝试保存,且SessionRepositoryFilter#commitSession()中仍存在本该被移除的当前会话对象。
使用环境:Spring Boot 3.2.3(无Spring Security)、Spring Cloud 2023.0.1、Redisson + RedisIndexedSessionRepository,flushmode为on_save,savemode为on_set_attribute,服务部署在Spring Cloud Gateway之后,移除网关测试时异常消失。
原因分析
- Spring Cloud Gateway时序问题:网关的转发、重试或请求头处理逻辑,可能导致后端服务收到重复请求或会话状态同步延迟,引发失效操作与保存操作的时序冲突。
- SessionRepositoryFilter逻辑漏洞:默认实现中,会话失效后未及时阻断后续的保存流程,导致已标记为失效的会话仍被提交保存。
解决方案
方案1:自定义RequestWrapper阻断失效会话保存
继承SessionRepositoryFilter.SessionRepositoryRequestWrapper,重写commitSession方法,跳过已失效会话的保存步骤:
public class CustomSessionRequestWrapper extends SessionRepositoryFilter.SessionRepositoryRequestWrapper { public CustomSessionRequestWrapper(HttpServletRequest request, HttpServletResponse response, SessionRepository<?> sessionRepository) { super(request, response, sessionRepository); } @Override protected void commitSession() { HttpSession session = getSession(false); if (session instanceof RedisIndexedSessionRepository.RedisSession redisSession) { // 会话已失效则直接返回,不执行保存 if (redisSession.isInvalidated()) { return; } } super.commitSession(); } }
再自定义SessionRepositoryFilter替换默认实现,使用上述包装类:
@Component public class CustomSessionRepositoryFilter extends SessionRepositoryFilter<RedisIndexedSessionRepository.RedisSession> { public CustomSessionRepositoryFilter(RedisIndexedSessionRepository sessionRepository) { super(sessionRepository); } @Override protected SessionRepositoryRequestWrapper wrapRequest(HttpServletRequest request, HttpServletResponse response) { return new CustomSessionRequestWrapper(request, response, getSessionRepository()); } }
方案2:调整Session保存策略
修改Redis Session的保存模式,确保失效状态及时同步到Redis,减少时序冲突概率。在application.yml中配置:
spring: session: redis: save-mode: always flush-mode: immediate
注意:该配置会增加Redis写入频次,需评估对性能的影响。
方案3:Logout端点显式清理会话状态
在调用invalidate()后,显式清除会话属性并从请求中移除会话对象:
@GetMapping("/logout") public String logout(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null) { // 清除所有会话属性 Enumeration<String> attrNames = session.getAttributeNames(); while (attrNames.hasMoreElements()) { session.removeAttribute(attrNames.nextElement()); } session.invalidate(); // 从请求中移除会话引用 request.setAttribute(HttpSession.class.getName(), null); } return "logout-success"; }
方案4:排查网关配置
检查Spring Cloud Gateway的路由、重试配置,关闭不必要的重试机制,避免重复请求引发的会话冲突:
spring: cloud: gateway: routes: - id: your-service-route uri: lb://your-service predicates: - Path=/api/** filters: # 移除重试过滤器(如果配置了的话) # - Retry=...
内容的提问来源于stack exchange,提问作者sge

