设置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=DEBUGCreating 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

