在 @GlobalHandler中无法获取正确的消息
@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”,而不是通用包装消息。
为什么会这样
- 失败原因: 你的
CustomJWTDecoder抛出一个JwtException。 - 包装器:
JwtAuthenticationProvider捕获了这个并抛出一个InsufficientAuthenticationException(AuthenticationException的子类)。 - 入口点: 你的
CustomAuthenticationEntryPoint被触发。 - 转发: 如果
HandlerExceptionResolver未能立即提交响应,Servlet容器会看到未处理的错误并转发到/error。 - 解决方法: 通过使用
request.getAttribute("jakarta.servlet.error.request_uri"),你可以穿过转发,看到用户实际尝试访问的目标。
最后一次检查
确保你的 PUBLIC_ENDPOINT 包含路径 /error。如果错误路径本身受到Spring Security的保护,AuthenticationEntryPoint 可能会再次触发,形成循环或掩盖原始错误信息。
Java
req.requestMatchers("/error").permitAll() // Add this
你想让我演示如何自定义 JwtAuthenticationConverter,在它还未到达入口点之前就处理这个逻辑吗?