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

GlobalExceptionHandler处理401错误时不显示ErrorDetails详情排查

问题根因
  • 认证相关逻辑运行在Servlet过滤器链层级,执行时机早于DispatcherServlet,@ControllerAdvice仅能处理进入Spring MVC调度流程、控制器层抛出的异常,过滤器层抛出的认证类异常不会被它捕获。
  • 当前全局异常处理器仅匹配Exception.class且固定返回500状态码,未认证场景抛出的AuthenticationException、AccessDeniedException等安全框架异常会被默认异常处理器拦截,仅返回状态码,不会写入自定义的ErrorDetails响应体。
  • ErrorDetails类仅提供全参构造,缺少无参构造、序列化相关配置时,Jackson无法正常序列化对象,也会导致响应体为空。
解决方案

1. 自定义安全框架异常入口点,处理过滤器层认证/授权异常

过滤器层抛出的异常无法被@ControllerAdvice捕获,需要单独实现Spring Security提供的异常处理接口:

/**
 * 未认证异常处理器:用户未登录、Token无效、Token过期时触发
 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");
        ErrorDetails errorDetails = new ErrorDetails(
                new Date(),
                "未认证,请先完成登录后再访问接口",
                request.getRequestURI()
        );
        ObjectMapper objectMapper = new ObjectMapper();
        response.getWriter().write(objectMapper.writeValueAsString(errorDetails));
        response.getWriter().flush();
    }
}

/**
 * 权限不足异常处理器:用户已登录但无目标接口访问权限时触发
 */
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType("application/json;charset=UTF-8");
        ErrorDetails errorDetails = new ErrorDetails(
                new Date(),
                "权限不足,无法访问当前接口",
                request.getRequestURI()
        );
        ObjectMapper objectMapper = new ObjectMapper();
        response.getWriter().write(objectMapper.writeValueAsString(errorDetails));
        response.getWriter().flush();
    }
}

2. 将自定义异常处理器注册到Spring Security安全链

在安全配置类中注入上述两个处理器,替换默认的异常处理逻辑:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private RestAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private RestAccessDeniedHandler accessDeniedHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/auth/**", "/public/**").permitAll()
                        .anyRequest().authenticated()
                )
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);
        return http.build();
    }
}

3. 优化现有全局异常处理器

调整异常匹配逻辑,针对不同异常类型返回对应HTTP状态码,补全序列化必要的类注解:

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    // 处理自定义业务异常
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorDetails> handleBusinessException(BusinessException ex, WebRequest webRequest) {
        ErrorDetails errorDetails = new ErrorDetails(
                new Date(),
                ex.getMessage(),
                webRequest.getDescription(false)
        );
        return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
    }

    // 兜底处理其余未捕获异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorDetails> handleGlobalException(Exception ex, WebRequest webRequest) {
        ErrorDetails errorDetails = new ErrorDetails(
                new Date(),
                "服务内部异常,请稍后重试",
                webRequest.getDescription(false)
        );
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

4. 修正ErrorDetails类配置

补全序列化需要的构造方法和注解,避免JSON序列化失败:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ErrorDetails {
    private Date timestamp;
    private String message;
    private String details;
}

如果使用JWT等自定义认证过滤器,需要在过滤器内部的doFilter方法中用try-catch包裹认证逻辑,捕获到认证异常后直接转发给对应的异常入口点处理,不要直接把异常抛到外层过滤器链。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 05:36:30