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

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处理:

  1. 在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执行
}
  1. 编写一个处理该路径的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));
    }
}
  1. 在Security配置中开放该路径的访问权限:
.authorizeHttpRequests(auth -> auth
        .requestMatchers("/api/auth/**", "/api/error/**").permitAll()
        .anyRequest().authenticated()
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 22:12:20