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

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会自动终止请求流转:

  1. 修改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);
}
  1. 调整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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 05:07:32