You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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之后,移除网关测试时异常消失。

原因分析

  1. Spring Cloud Gateway时序问题:网关的转发、重试或请求头处理逻辑,可能导致后端服务收到重复请求或会话状态同步延迟,引发失效操作与保存操作的时序冲突。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 01:05:12