I'm setting up jwt authentication in my spring-boot application and I want to handle all exceptions in one place.
I know that exceptions from Controllers can be handled in @ControllerAdvice,
for FilterSecurityInterceptor I can set AuthenticationEntryPoint and AccessDeniedManager, and for AuthenticationFilters I cans set UnsuccessfulAuthentication manager. But that seems like far too many places to handle exceptions.
So I want to make it more centralized.
I decided to simply rethrow all the exceptions, so that Tomcat redirects the request to "/error" and sets exception details as request attributes. "/error" is handled by Controller, that extracts the exception form the request and rethrows it, so exception goes directly to the exception handler.
At my authentication filters (I've got a couple of them) I do this in the constructor: this.setAuthenticationFailureHandler(
ExceptionHandlerUtils::authenticationFailureHandler);
public static void authenticationFailureHandler(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) {
throw authenticationException;
}
And the same for the FilterSecurityInterceptor (in security configuration):
@Override
protected void configure(HttpSecurity http) throws Exception {
...
http
.authenticationEntryPoint(ExceptionHandlerUtils::authenticationEntryPoint)
.accessDeniedHandler(ExceptionHandlerUtils::accessDeniedHandler);
...
}
Then, because of unhandled exception (filterChain doesn't handle exceptions (except some AuthenticatoinException, etc), request processing rolls back to the org.apache.catalina.core.StandardWrapperValve, that finally catches it, does this:
try {
*shortly, filterChain.doFilter(...)*
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
}
Here happens unwanted logging, and exception(...) method sets exception as request attribute, then org.apache.catalina.core.StandardHostValve#throwable(...) sets some other request attributes and redirects the request to "/error". Than request goes through chain again to my RethrowingErrorController
@RequestMapping("/error")
public Object getError(HttpServletRequest request) throws Throwable {
Object val = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
if (val != null) {
throw (Throwable) val;
}
/* this should never be called, but whatever */
return "why are you here???";
}
And finally is handled by
@ControllerAdvice
@Log4j2
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({RuntimeException.class})
protected ResponseEntity<Object> handleUnexpectedException(
RuntimeException ex, WebRequest request) {
log.error("unexpected conflict:", ex);
Map<?, ?> body = ExceptionHandlerUtils.getErrorAttributes(request, false);
return handleExceptionInternal(ex, body, new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
This approach allows me to handle all exceptions that happen in any Controller or Filter (like JwtAuthorizationFilter) in SpringSecurityFilterChain in one place.
However, before tomcat redirects request to "/error" as described above, it complains about "internal error" (my exception) in logs. I don't want that in logs, because I log errors in @ControllerAdvice anyway, and also I don't want to disable tomcat logs completely.
I can disable logs from the whole package org.apache.catalina.core; in application.yml
logging:
level:
org.apache.catalina.core: off
But I am worried, that I might miss something important later. So, I need either a better solution for centralizing exceptions, or a solid way to hide unwanted error logs.
All suggestions to improve my question are very welcome!