Spring Boot自定义异常处理器无法捕获MalformedJwtException问题
问题原因分析
你的核心问题在于:JwtAuthenticationFilter属于Spring Security的过滤器,执行顺序在DispatcherServlet之前,而@ControllerAdvice(包括其中的@ExceptionHandler)只能处理DispatcherServlet处理请求流程中(即Controller层及之后)抛出的异常。过滤器阶段抛出的异常根本无法到达全局异常处理器,所以你设置@Order也不会生效。
解决方案
针对Spring Security中JWT令牌相关的异常(无令牌、令牌无效、格式错误等),最贴合框架设计的方案是使用AuthenticationEntryPoint,以下是具体实现步骤:
1. 自定义AuthenticationEntryPoint实现类
这个类专门处理未认证/认证失败的场景,直接返回自定义JSON响应:
@Component public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { private final ObjectMapper objectMapper; // 注入Spring默认的ObjectMapper public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { // 自定义响应消息,优先使用异常自带信息,否则用默认提示 String message = "To access this resource, you must provide a valid security token"; if (authException != null && authException.getMessage() != null && !authException.getMessage().isEmpty()) { message = authException.getMessage(); } // 组装响应体 ResponseWrapper responseWrapper = new ResponseWrapper(HttpStatus.UNAUTHORIZED, message); // 设置响应头和状态码 response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); // 写入JSON响应 objectMapper.writeValue(response.getWriter(), responseWrapper); } }
2. 在SecurityFilterChain中配置该EntryPoint
修改你的Spring Security配置类,将自定义的AuthenticationEntryPoint绑定到异常处理逻辑:
@Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CustomAuthenticationEntryPoint customAuthenticationEntryPoint) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.customAuthenticationEntryPoint = customAuthenticationEntryPoint; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() // 开放认证接口 .anyRequest().authenticated() ) // 配置异常处理,指定自定义的AuthenticationEntryPoint .exceptionHandling(exception -> exception .authenticationEntryPoint(customAuthenticationEntryPoint) ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
3. 调整JwtAuthenticationFilter的异常处理逻辑
你可以简化Filter中的代码,直接让JWT相关异常抛出,Spring Security会自动将其交给AuthenticationEntryPoint处理:
try { jwt = authHeader.substring(authHeaderStartsWith.length()); userEmail = jwtService.extractUsername(jwt); } catch (MalformedJwtException | IllegalArgumentException ex) { // 直接抛出Spring Security的AuthenticationException子类,或者自定义异常 throw new BadCredentialsException("Invalid JWT token", ex); } catch (Exception ex) { throw new AuthenticationServiceException("Failed to process JWT token", ex); }
备选方案(不推荐,仅作补充)
如果坚持要通过全局异常处理器捕获Filter中的异常,可以将异常转发到DispatcherServlet处理:
- 在Filter的catch块中,将异常存入request属性,然后转发到专门的异常处理接口:
try { jwt = authHeader.substring(authHeaderStartsWith.length()); userEmail = jwtService.extractUsername(jwt); } catch (Exception ex) { request.setAttribute("jwtException", new CustomJWTException("To access this resource, you must provide a valid security token", HttpStatus.UNAUTHORIZED)); request.getRequestDispatcher("/api/error/handle-jwt").forward(request, response); return; // 必须终止后续Filter执行 }
- 编写一个处理该路径的Controller:
@RestController @RequestMapping("/api/error") public class JwtErrorController { @GetMapping("/handle-jwt") public ResponseEntity<ResponseWrapper> handleJwtException(HttpServletRequest request) { CustomJWTException ex = (CustomJWTException) request.getAttribute("jwtException"); String message = ex.getMessage() != null && !ex.getMessage().isEmpty() ? ex.getMessage() : "To access this resource, you must provide a valid security token"; return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ResponseWrapper(HttpStatus.UNAUTHORIZED, message)); } }
- 在Security配置中开放该路径的访问权限:
.authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**", "/api/error/**").permitAll() .anyRequest().authenticated() )
内容的提问来源于stack exchange,提问作者musicinmusic
相关产品推荐
相关产品推荐

