为何@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接口,专门处理未认证/认证失败的场景,能覆盖过滤器中抛出的异常:
- 实现自定义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); } }
- 修改过滤器,将自定义异常存入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); }
- 在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
相关产品推荐
相关产品推荐

