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

为何@RestControllerAdvice无法处理JwtAuthFilter抛出的过期JWT异常?

问题原因与解决方案

为什么过滤器抛出的异常无法被@RestControllerAdvice捕获

@RestControllerAdvice是Spring MVC的组件,只负责处理DispatcherServlet调度范围内的异常——也就是请求到达Controller层之后抛出的异常。而你的JwtAuthFilter属于Spring Security过滤器链,它的执行顺序在DispatcherServlet之前。当过滤器抛出异常时,请求还没进入MVC的处理流程,全局异常处理器根本接收不到这个异常,最终会被Spring Security默认的异常处理机制拦截,返回403 Forbidden。

解决方案

方案1:在过滤器中直接返回自定义响应

不需要抛异常,直接在过滤器里构造JSON响应并写入HttpServletResponse,同时终止过滤器链:

修改JwtAuthFilter中的catch块:

catch (ExpiredJwtException e) {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    
    ErrorResponse errorResponse = new ErrorResponse(
            HttpStatus.UNAUTHORIZED.value(),
            "Unauthorized",
            "Expired token"
    );
    
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.writeValue(response.getWriter(), errorResponse);
    return; // 停止后续过滤器执行
}
catch (JwtException e) {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    
    ErrorResponse errorResponse = new ErrorResponse(
            HttpStatus.UNAUTHORIZED.value(),
            "Unauthorized",
            "Invalid token"
    );
    
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.writeValue(response.getWriter(), errorResponse);
    return;
}

方案2:自定义AuthenticationEntryPoint处理认证异常

Spring Security提供AuthenticationEntryPoint接口,专门处理未认证/认证失败的场景,能覆盖过滤器中抛出的异常:

  1. 实现自定义AuthenticationEntryPoint:
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    private final ObjectMapper objectMapper;

    public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        // 从request中获取过滤器抛出的自定义异常
        Exception exception = (Exception) request.getAttribute("filterException");
        String errorMsg = exception instanceof InvalidTokenException ? exception.getMessage() : "Authentication failed";

        ErrorResponse errorResponse = new ErrorResponse(
                HttpServletResponse.SC_UNAUTHORIZED,
                "Unauthorized",
                errorMsg
        );

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        objectMapper.writeValue(response.getWriter(), errorResponse);
    }
}
  1. 修改过滤器,将自定义异常存入request,再抛出Spring Security标准认证异常:
catch (ExpiredJwtException e) {
    InvalidTokenException ex = new InvalidTokenException("Expired token");
    request.setAttribute("filterException", ex);
    throw new AuthenticationCredentialsNotFoundException(ex.getMessage(), ex);
}
catch (JwtException e) {
    InvalidTokenException ex = new InvalidTokenException("Invalid token");
    request.setAttribute("filterException", ex);
    throw new AuthenticationCredentialsNotFoundException(ex.getMessage(), ex);
}
  1. 在SecurityConfig中配置这个EntryPoint:
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig {
    private final AuthenticationProvider authenticationProvider;
    private JwtAuthFilter jwtAuthFilter;
    private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("api/v1/auth/**").permitAll()
                        .anyRequest().authenticated()
                )
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling(ex -> ex.authenticationEntryPoint(customAuthenticationEntryPoint)) // 配置自定义异常入口
                .build();
    }
}

方案3:复用现有GlobalExceptionHandler(可选)

如果想复用已有的GlobalExceptionHandler,可以将过滤器异常转发到MVC错误流程:

修改过滤器catch块:

catch (ExpiredJwtException e) {
    request.setAttribute("javax.servlet.error.exception", new InvalidTokenException("Expired token"));
    request.getRequestDispatcher("/error").forward(request, response);
    return;
}
catch (JwtException e) {
    request.setAttribute("javax.servlet.error.exception", new InvalidTokenException("Invalid token"));
    request.getRequestDispatcher("/error").forward(request, response);
    return;
}

这种方式需要确保Spring MVC的错误配置正确,相对前两种方案繁琐,优先推荐方案1或2。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 16:43:19