Grails 6.x中Spring Security处理AJAX会话超时返回302而非401问题
核心问题
你的自定义InvalidSessionStrategy和CustomAuthenticationEntryPoint未触发,本质是Grails 6.x集成Spring Security时,默认过滤器链优先级高于自定义配置,导致会话超时的请求被默认逻辑拦截并返回302重定向,而非进入自定义处理逻辑。
可能的原因
Grails Spring Security插件的自动配置覆盖
Grails的spring-security-core插件会自动生成默认的SecurityFilterChain,如果你的自定义SecurityConfiguration没有被正确加载或优先级低于默认配置,就会被覆盖,导致自定义策略不生效。过滤器顺序问题
Spring Security的过滤器链是按顺序执行的,如果处理会话超时的默认过滤器(如SessionManagementFilter)在你的自定义逻辑之前执行,会直接返回302,跳过你的策略类。AJAX请求头验证失败
前端AJAX请求可能未正确携带X-Requested-With: XMLHttpRequest头,导致后端判断不是AJAX请求,执行了普通重定向逻辑(即使策略触发了,也会走else分支)。无效会话的触发条件不匹配
InvalidSessionStrategy仅在请求携带无效Session ID时触发;如果会话超时后前端未携带Session ID(比如Cookie已过期),会触发AuthenticationEntryPoint而非前者,需确认你的测试场景对应哪种情况。
解决方向
1. 确保自定义配置的优先级高于插件默认配置
在Grails中,纯Java的@Configuration类需要通过特定方式确保被优先加载,或者直接使用Grails插件的配置方式:
- 给
SecurityConfiguration添加@Order(Ordered.HIGHEST_PRECEDENCE)注解,确保它的过滤器链最先被加载:@Configuration @Order(Ordered.HIGHEST_PRECEDENCE) class SecurityConfiguration { // ... 原有代码 } - 或者在
grails-app/conf/application.groovy中直接配置策略类(Grails推荐方式):grails.plugin.springsecurity.sessionManagement.invalidSessionStrategy = bean(AjaxInvalidSessionStrategy) grails.plugin.springsecurity.exceptionHandling.authenticationEntryPoint = bean(CustomAuthenticationEntryPoint)
2. 禁用默认的会话超时重定向
除了设置invalidSessionUrl(null),还需确保Spring Security不会自动添加默认的重定向逻辑:
http.sessionManagement(session -> session .invalidSessionUrl(null) .invalidSessionStrategy(new AjaxInvalidSessionStrategy()) .sessionAuthenticationStrategy(new SessionFixationProtectionStrategy()) // 保持必要的会话保护 )
3. 调试过滤器链执行流程
- 在自定义策略类的方法中添加日志输出,确认是否真的未触发:
@Override public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { System.out.println("InvalidSessionStrategy triggered"); // 原有逻辑 } - 使用Debug模式跟踪请求,查看
FilterChainProxy中执行的过滤器顺序,确认自定义逻辑是否被跳过。
4. 验证AJAX请求头
- 前端调试确认AJAX请求是否携带
X-Requested-With: XMLHttpRequest头;如果是使用Axios等框架,需手动配置:axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; - 后端可在过滤器中先打印请求头,确认是否收到该标识:
System.out.println("X-Requested-With: " + request.getHeader("X-Requested-With"));
5. 覆盖完整的异常处理逻辑
确保CustomAuthenticationEntryPoint覆盖所有未认证的场景(包括无Session ID的情况),同时检查是否有其他异常处理器(如AccessDeniedHandler)影响AJAX请求:
http.exceptionHandling(handling -> handling .authenticationEntryPoint(new CustomAuthenticationEntryPoint()) .accessDeniedHandler((request, response, ex) -> { boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); if (isAjax) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"); } else { response.sendRedirect(request.getContextPath() + "/login?accessDenied=true"); } }) )
内容的提问来源于stack exchange,提问作者NiBE

