How to centralize exception-handling in spring-boot application with spring security?

Viewed 758

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!

0 Answers
Related