Spring Boot 3.4.4中sendError不生效及过滤器链流转异常问题
问题解决:Spring Boot 3.4.4升级后JWT认证请求流转到后续过滤器链的问题
核心问题原因
你的JWT认证过滤器在处理无效/缺失令牌时,仅设置了响应状态码,但仍调用了filterChain.doFilter(request, response),导致请求继续流转到后续的WebConfiguration过滤器链(其securityMatcher为/**,会匹配所有请求)。同时Spring Boot 3.x对应的Spring Security 6.x对过滤器链的执行逻辑做了调整,即使第一个链已匹配,只要请求未被终止,后续匹配的链仍会执行。
解决方案
方案1:终止过滤器链执行(最直接)
修改JWT认证过滤器的doFilterInternal方法,当令牌无效/缺失时,不调用过滤器链的doFilter方法,直接完成响应:
protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { final JwtTokenUtility jwtTokenUtility = new JwtTokenUtility(appConfig.getApiJwtSecret()); boolean shouldContinue = true; jwtTokenUtility.getToken(request).ifPresentOrElse(token -> { final JwtTokenStatus tokenStatus = jwtTokenUtility.getTokenStatus(token); switch (tokenStatus) { case EXPIRED, REVOKED, INVALID -> { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); shouldContinue = false; } case VALID -> { final String subject = jwtTokenUtility.getSubject(token); final UserDetails details = userDetails.loadUserByUsername(subject); final UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } }, () -> { log.debug("Missing JWT Token"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); shouldContinue = false; }); // 只有当认证通过时才继续执行过滤器链 if (shouldContinue) { filterChain.doFilter(request, response); } }
方案2:利用Spring Security的异常处理机制(更符合框架规范)
放弃手动设置响应状态码,改为抛出AuthenticationException,让配置的ApiAuthenticationEntryPoint处理,Spring Security会自动终止请求流转:
- 修改JWT过滤器:
protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { final JwtTokenUtility jwtTokenUtility = new JwtTokenUtility(appConfig.getApiJwtSecret()); jwtTokenUtility.getToken(request).ifPresentOrElse(token -> { final JwtTokenStatus tokenStatus = jwtTokenUtility.getTokenStatus(token); switch (tokenStatus) { case EXPIRED -> throw new AuthenticationCredentialsNotFoundException("JWT token expired"); case REVOKED -> throw new AuthenticationCredentialsNotFoundException("JWT token revoked"); case INVALID -> throw new BadCredentialsException("Invalid JWT token"); case VALID -> { final String subject = jwtTokenUtility.getSubject(token); final UserDetails details = userDetails.loadUserByUsername(subject); final UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } }, () -> { log.debug("Missing JWT Token"); throw new AuthenticationCredentialsNotFoundException("Missing JWT token"); }); filterChain.doFilter(request, response); }
- 调整
ApiAuthenticationEntryPoint增强异常信息:
@Slf4j public class ApiAuthenticationEntryPoint implements AuthenticationEntryPoint { public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException { log.debug("Pre-authenticated entry point called. Rejecting access: {}", ex.getMessage()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "API Request Denied (Authentication Failed): " + ex.getMessage()); } }
方案3:调整Web过滤器链的匹配规则(从根源避免冲突)
修改WebConfiguration的securityMatcher,排除API路径,让它只处理非API请求:
@Order() @Configuration public class WebConfiguration { private static final String[] NON_API_PATHS = {"/**", "!" + Constants.API_BASE_PATH + "**"}; @Bean public SecurityFilterChain filterChain(HttpSecurity http, RememberMeServices rememberMe) throws Exception { http .securityContext(securityContext -> securityContext.requireExplicitSave(false)) .csrf(csrf -> { csrf.ignoringRequestMatchers(CSRF_DISABLED_PATHS); CsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler(); requestHandler.setCsrfRequestAttributeName("_csrf"); csrf.csrfTokenRequestHandler(requestHandler); } ) .securityMatcher(NON_API_PATHS) // 排除API路径 .authorizeHttpRequests(requests -> requests .requestMatchers(AUTHORIZED_PATHS).permitAll() .requestMatchers("/dashboard").hasAuthority("ADMIN") .anyRequest().authenticated() ) // 其余配置保持不变... .sessionManagement(sessionManagement -> sessionManagement.requireExplicitAuthenticationStrategy(true)); return http.build(); } }
推荐方案
优先选择方案2,因为它更符合Spring Security的设计规范,利用框架的异常处理机制统一管理认证失败场景;如果需要快速修复,方案1也能立即生效;方案3则从过滤器链匹配层面避免了冲突,适合长期架构优化。
内容的提问来源于stack exchange,提问作者user29804693
相关产品推荐
相关产品推荐

