Spring Boot OAuth2 Resource Server自定义404错误异常处理问题
问题:Spring Boot + OAuth2 Resource Server下自定义404响应被AuthenticationEntryPoint拦截
问题现象
访问不存在的API时,始终返回自定义的401未授权响应,而非预期的404格式响应。已配置RestControllerAdvice处理NoHandlerFoundException,也添加了相关配置参数,但异常始终被CustomAuthenticationEntryPoint捕获。
现有配置代码
SecurityFilterChain配置
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) .cors().and() .authorizeHttpRequests(auths -> auths .antMatchers(ALLOW_LIST_URIS).permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(jwtAuthenticationConverter()))) .oauth2ResourceServer(oAuth2 -> oAuth2.authenticationEntryPoint(authenticationEntryPoint)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .build(); }
CustomAuthenticationEntryPoint实现
@Slf4j @Component @RequiredArgsConstructor public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { private final Jackson2ObjectMapperBuilder mapperBuilder; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { log.warn("Someone has tried to access without Authentication(AuthenticationException): {}", authException.getMessage()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); mapperBuilder.build() .writeValue(response.getOutputStream(), new ErrorResDto(HttpStatus.UNAUTHORIZED, UNAUTHORIZED_MESSAGE)); response.getOutputStream().flush(); } }
已尝试的配置参数
spring.mvc.throw-exception-if-no-handler-found=true server.error.whitelabel.enabled=false spring.web.resources.add-mappings=false
自定义异常处理器(RestControllerAdvice)
@Slf4j @RestControllerAdvice public class CustomExceptionHandling { @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity<ErrorResDto> handleNoHandlerFoundException (NoHandlerFoundException e) { log.error("NoHandlerFoundException init"); return createHttpResponse(HttpStatus.NOT_FOUND, MAPPING_NOT_FOUND); } private ResponseEntity<ErrorResDto> createHttpResponse(HttpStatus httpStatus, String message) { ErrorResDto errorResponse = new ErrorResDto(httpStatus, message); return new ResponseEntity<>(errorResponse, errorResponse.getHttpStatus()); } }
当前与期望响应
当前错误响应(401)
{ "rc": 401, "message": "Full authentication is required to access this resource", "httpStatus": "UNAUTHORIZED" }
期望的404响应
{ "rc": 404, "message": "Path not found", "httpStatus": "NOT_FOUND" }
解决方法
原因分析
Spring Security过滤器链执行优先级高于Spring MVC的DispatcherServlet。访问不存在的API时,Security先拦截请求并检查认证状态,若未携带有效凭证,直接触发AuthenticationEntryPoint返回401,DispatcherServlet没有机会处理请求并抛出NoHandlerFoundException。
具体解决方案
方案1:在AuthenticationEntryPoint中提前判断请求路径是否存在
注入RequestMappingHandlerMapping,在处理认证异常前先检查请求是否有对应的处理器,无匹配则直接返回404:
@Slf4j @Component @RequiredArgsConstructor public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { private final Jackson2ObjectMapperBuilder mapperBuilder; private final RequestMappingHandlerMapping requestMappingHandlerMapping; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { try { // 查找当前请求对应的处理器 HandlerExecutionChain handlerExecutionChain = requestMappingHandlerMapping.getHandler(request); if (handlerExecutionChain == null) { // 无匹配处理器,返回404 response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_NOT_FOUND); mapperBuilder.build() .writeValue(response.getOutputStream(), new ErrorResDto(HttpStatus.NOT_FOUND, "Path not found")); response.getOutputStream().flush(); return; } } catch (Exception e) { log.error("Failed to check request handler", e); } // 有匹配处理器,返回401 log.warn("Someone has tried to access without Authentication(AuthenticationException): {}", authException.getMessage()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); mapperBuilder.build() .writeValue(response.getOutputStream(), new ErrorResDto(HttpStatus.UNAUTHORIZED, UNAUTHORIZED_MESSAGE)); response.getOutputStream().flush(); } }
方案2:使用ErrorController统一处理所有错误
实现ErrorController,统一管理404、401等所有错误响应,避免Security与MVC的异常处理冲突:
@RestController @RequestMapping("/error") public class CustomErrorController implements ErrorController { private final ErrorAttributes errorAttributes; public CustomErrorController(ErrorAttributes errorAttributes) { this.errorAttributes = errorAttributes; } @GetMapping public ResponseEntity<ErrorResDto> handleError(HttpServletRequest request) { HttpStatus status = getStatus(request); String message = switch (status.value()) { case 404 -> "Path not found"; case 401 -> "Full authentication is required to access this resource"; default -> status.getReasonPhrase(); }; return ResponseEntity.status(status).body(new ErrorResDto(status, message)); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } try { return HttpStatus.valueOf(statusCode); } catch (Exception ex) { return HttpStatus.INTERNAL_SERVER_ERROR; } } }
内容的提问来源于stack exchange,提问作者helmigandi
相关产品推荐
相关产品推荐

