javaspringspring-bootspring-rest

Exception not throwed to global api error handler


I defined class for global api error handler in spring boot 3.2.0. I added security with bearer token, I expect to return response but it says 14:41:32.831 [http-nio-0.0.0.0-11150-exec-2] ERROR o.a.c.c.C.[Tomcat].[localhost] - Exception Processing ErrorPage[errorCode=0, location=/error]

I write global api error handler class

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ApiGlobalErrorHandler {

    private static final Logger LOG = LoggerFactory.getLogger(ApiGlobalErrorHandler.class.getName());

    @ExceptionHandler(UnauthorizedAccessException.class)
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    @ResponseBody
    protected ApiResponseWrapper handleUnauthorizedAccessException(UnauthorizedAccessException ex) {
        LOG.error("UnauthorizedAccessException occurred:{},code-{}", ex.getErrorType(), ex.getErrorType().getCode());
        return new ApiResponseWrapper(new ApiErrorResponse(HttpStatus.UNAUTHORIZED.value() + "." + API_ID_CODE + "." + ex.getErrorType().getCode(), ex.getErrorType().getMessagge()));
    }

I expected the error response but I got 14:41:32.831 [http-nio-0.0.0.0-11150-exec-2] ERROR o.a.c.c.C.[Tomcat].[localhost] - Exception Processing ErrorPage[errorCode=0, location=/error]. how to disable processing error page? Trying without bearer token enter image description here stack trace is:

14:41:32.831 [http-nio-0.0.0.0-11150-exec-2] ERROR o.a.c.c.C.[Tomcat].[localhost] - Exception Processing ErrorPage[errorCode=0, location=/error]
rs.yettel.roamingbarring.exception.UnauthorizedAccessException: Missing Authorization header
    at rs.yettel.roamingbarring.security.ApiKeyAuthFilter.getPreAuthenticatedPrincipal(ApiKeyAuthFilter.java:19)
    at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doAuthenticate(AbstractPreAuthenticatedProcessingFilter.java:189)
    at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doFilter(AbstractPreAuthenticatedProcessingFilter.java:142)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)
    at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:642)
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:410)
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:340)
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:277)
    at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:362)
    at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:222)
    at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:308)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:149)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:833)


Solution

  • Starting from Spring Security version 6.0, the framework began filtering all dispatcher types, such as request, async, error, forward, and include.

    If you want to exclude the error dispatcher type in your scenario, you can configure the filter chain as shown below:

    public SecurityFilterChain customFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeHttpRequests(auth -> auth
                        .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
                        .requestMatchers("/error").permitAll()
                        .anyRequest().authenticated()
                )
                .addFilterBefore(new ApiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        return httpSecurity.build();
    }
    

    For additional information on this topic, refer to the Spring Security documentation at this link: https://docs.spring.io/spring-security/reference/5.8/migration/servlet/authorization.html#switch-filter-all-dispatcher-types.

    It appears that your ApiKeyAuthFilter is raising an UnauthorizedAccessException. It's important to note that exceptions thrown within Filters cannot be handled using @ControllerAdvice. To gain insights into customizing exceptions thrown within Filters, you may refer to this question for guidance.

    EDIT

    After reviewing your code, I have several points to highlight:

    1. As mentioned earlier, exceptions raised in the filters are not handled by your @ControllerAdvice. To send a custom error message, it is recommended to catch the error within the filter and send the error code using HttpServletResponse. I have provided a sample repository demonstrating how to achieve this, which you can find here: GitHub Repository.

    2. I noticed that you are using AbstractPreAuthenticatedProcessingFilter in your case. However, if you are only overriding the abstract methods and those methods do not have access to HttpServletResponse, you won't be able to send a custom error code and/or message using this approach. In the sample repository I created, you can observe the use of CustomSecurityFilter, which extends OncePerRequestFilter and is added before running BasicAuthenticationFilter. This approach is considered a cleaner way to achieve the desired functionality.

    For further customization, I recommend exploring the BasicErrorController and DefaultErrorAttributes classes in the Spring framework. Additionally, you can refer to the Spring Boot official documentation for more information: Spring Boot Documentation.