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

设置SessionCreationPolicy.STATELESS后Spring Security仍存会话问题求助

问题分析与解决方案

首先,你遇到的核心问题是虽然配置了SessionCreationPolicy.STATELESS,但Spring Security仍然在创建会话,导致无令牌请求还能通过认证,这通常有几个常见的原因,咱们一步步排查和解决:

1. 自定义过滤器可能触发了会话创建

你的customOAuth2Filter()可能在认证过程中调用了SecurityContextHolder.getContext().setAuthentication(auth),但如果没有正确处理上下文清理,Spring Security的后续逻辑可能会默认创建会话来保存认证信息。即使你设置了STATELESS,某些过滤器或认证逻辑仍可能意外触发会话创建。

解决方法:

  • 在自定义过滤器中,确保认证成功后不依赖会话保存认证信息,每次请求都通过令牌重新解析认证。
  • 显式在请求结束后清理安全上下文,避免线程复用导致的信息泄漏:
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            // 你的令牌解析和认证逻辑
            Authentication auth = parseTokenAndAuthenticate(request);
            if (auth != null) {
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
            filterChain.doFilter(request, response);
        } finally {
            // 强制清理上下文,防止会话被意外创建
            SecurityContextHolder.clearContext();
        }
    }
    

2. 检查是否有其他组件隐式创建会话

除了自定义过滤器,Spring Security的默认过滤器(比如RequestCacheAwareFilter)或者你的应用代码中,可能存在调用request.getSession()的地方——不管是显式还是隐式调用,都会触发会话创建,哪怕你设置了STATELESS。

排查方法:

  • 启用Spring Security调试日志,定位会话创建的触发点:
    在application.properties中添加:
    logging.level.org.springframework.security=DEBUG
    logging.level.org.springframework.web.servlet=DEBUG
    
    日志中会出现Creating new session的记录,跟着日志找到对应的代码位置。
  • 替换所有request.getSession()为request.getSession(false)(不会创建新会话),或者在无状态场景下完全避免调用会话相关方法。

3. 解决JSESSIONID不匹配的问题

这种情况是因为每次请求都在创建新会话,但浏览器还保留着旧的会话Cookie,导致请求携带旧JSESSIONID,服务器却返回新的Set-Cookie。这说明会话在被频繁创建,完全不符合STATELESS的预期。

解决方法:

  • 强化会话禁用配置,添加无效会话的处理逻辑:
    http.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .invalidSessionStrategy((request, response) -> {
            // 无令牌请求直接返回401,不处理会话
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid or missing authentication token");
        });
    
  • 确保自定义过滤器在认证失败时,直接返回401,不设置任何会话Cookie。

4. 检查CORS配置是否保留会话

你的配置中启用了cors(),如果CORS配置允许携带凭证(allowCredentials = true),浏览器可能会保留会话Cookie,干扰无状态认证逻辑。

调整建议:

  • 如果不需要跨域携带凭证,设置allowCredentials = false,同时确保前端请求不携带withCredentials: true。
  • 如果必须允许凭证,更要严格控制会话创建,确保只有令牌认证生效,会话完全被禁用。

5. 验证认证逻辑是否真的依赖令牌

最后,确认你的自定义过滤器确实是每次请求都验证令牌,而不是在第一次认证后将信息存在会话中。比如,检查过滤器是否每次都从请求头(或其他约定位置)获取令牌、解析并验证,而不是从会话中读取认证信息。

举个标准的无状态过滤器逻辑示例:

public class CustomOAuth2Filter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = extractTokenFromRequest(request);
        if (token == null || !validateToken(token)) {
            // 无有效令牌直接返回401
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing or invalid authentication token");
            return;
        }
        // 从令牌解析用户信息,创建Authentication对象
        Authentication authentication = buildAuthenticationFromToken(token);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        try {
            filterChain.doFilter(request, response);
        } finally {
            SecurityContextHolder.clearContext();
        }
    }

    private String extractTokenFromRequest(HttpServletRequest request) {
        // 从Authorization头提取Bearer令牌
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }

    // 省略validateToken和buildAuthenticationFromToken的实现
}

按照以上步骤排查调整后,无令牌的请求应该会直接返回401,触发重新登录逻辑,同时也不会再创建不必要的会话了。

内容的提问来源于stack exchange,提问作者Katana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:35:01