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)
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:
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.
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.