在 @GlobalHandler中无法获取正确的消息

后端开发 2026-07-11
@Component
public class CustomAuthenticationEntryPoint  implements AuthenticationEntryPoint {

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        resolver.resolveException(request, response, null, authException);
    }
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) {
    httpSecurity
            .cors(Customizer.withDefaults())
            .csrf(csrf->csrf.disable())
            .authorizeHttpRequests(req->
            req.requestMatchers(PUBLIC_ENDPOINT).permitAll()
                    .requestMatchers("/admin/**").hasAuthority("SCOPE_ADMIN")
                    .anyRequest().hasAuthority("SCOPE_USER"));
    httpSecurity.oauth2ResourceServer(config->
            config.jwt(jwtConfigurer -> jwtConfigurer.decoder(jwtDecoder)).authenticationEntryPoint(customAuthenticationEntryPoint));

    return httpSecurity.build();
}
@ExceptionHandler({Unauthorized.class,AuthenticationException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleUnAuthorizedException(Exception e,WebRequest webRequest) {
    String message = e.getMessage();
    String path = webRequest.getDescription(false).replace("uri=","");
    ErrorResponse errorResponse = ErrorResponse.builder()
            .message(message)
            .path(path)
            .timestamp(new Date())
            .build();
    return errorResponse;
}

具有字段 isVerify:false 的令牌,会返回错误和对用户的响应,但消息和URI不正确。响应: "message":"需要对该资源进行完整的身份验证" "path":"/error"

异常:

Caused by: org.springframework.security.oauth2.jwt.JwtException: User not verified
    at vn.sinv.trading.Config.CustomJWTDecoder.decode(CustomJWTDecoder.java:36) ~[classes/:na]
    at org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider.getJwt(JwtAuthenticationProvider.java:100) ~[spring-security-oauth2-resource-server-7.0.3.jar:7.0.3]
    ... 62 common frames omitted

解决方案

你看到路径为 /error、以及通用消息的原因,是在你的 AuthenticationEntryPoint 将异常交给 HandlerExceptionResolver 时,请求已经被底层容器分发到错误页面,或者 WebRequest 上下文已经丢失/被包装。

具体来说,当一个 Filter(Security所在位置)抛出一个未在早期被捕获的异常时,Spring Boot的 BasicErrorController 接管,从而造成转发到 /error

下面是修复此问题的方法,以确保你的 @ExceptionHandler 能获取原始请求的详细信息。


1.修复 AuthenticationEntryPoint

HandlerExceptionResolver 需要知道要目标的控制器。将 null 作为处理程序传递时,有时难以正确映射上下文。更重要的是,我们应确保请求未被修改。

更新你的 commence 方法,使其更明确地使用 HandlerExceptionResolver

Java

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) 
        throws IOException, ServletException {
    // This tells Spring to treat this exception as if it happened inside a Controller
    resolver.resolveException(request, response, null, authException);
}

2.更新 @ExceptionHandler

当Filter中发生异常时,WebRequest 可能指向错误分发路径。你可以从Spring在错误分发期间填充的 HttpServletRequest 属性中提取原始的URI。

Java

@ExceptionHandler({AuthenticationException.class, JwtException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleUnAuthorizedException(Exception e, HttpServletRequest request) {

    // Check if there is a forwarded URI (the original request)
    String path = (String) request.getAttribute("jakarta.servlet.error.request_uri");
    if (path == null) {
        path = request.getRequestURI();
    }

    return ErrorResponse.builder()
            .message(e.getMessage()) // This will now catch "User not verified"
            .path(path)
            .timestamp(new Date())
            .build();
}

3.处理异常类型

在你的堆栈跟踪中,根本原因是 JwtException。尽管Spring Security最终会把它包装在 AuthenticationException 中,但如上所示向你的 @ExceptionHandler 添加 JwtException.class,可以确保解析器获取到具体的消息“User not verified”,而不是通用包装消息。


为什么会这样

  1. 失败原因: 你的 CustomJWTDecoder 抛出一个 JwtException
  2. 包装器: JwtAuthenticationProvider 捕获了这个并抛出一个 InsufficientAuthenticationExceptionAuthenticationException 的子类)。
  3. 入口点: 你的 CustomAuthenticationEntryPoint 被触发。
  4. 转发: 如果 HandlerExceptionResolver 未能立即提交响应,Servlet容器会看到未处理的错误并转发到 /error
  5. 解决方法: 通过使用 request.getAttribute("jakarta.servlet.error.request_uri"),你可以穿过转发,看到用户实际尝试访问的目标。

最后一次检查

确保你的 PUBLIC_ENDPOINT 包含路径 /error。如果错误路径本身受到Spring Security的保护,AuthenticationEntryPoint 可能会再次触发,形成循环或掩盖原始错误信息。

Java

req.requestMatchers("/error").permitAll() // Add this

你想让我演示如何自定义 JwtAuthenticationConverter,在它还未到达入口点之前就处理这个逻辑吗?

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章