简体   繁体   中英

How to catch the exceptions thrown by spring security matchers?

I have a backend protected by spring security configured with antMatchers() like .antMatchers("/api/myProtectedUri/*").authenticated()

In the same app I use @ControllerAdvice and @ExceptionHandler to handle exceptions thrown. To protect the API from returning uncontrolled responses from backend (like stacktraces) I wrote something like this:

@ExceptionHandler({RuntimeException.class})
public ResponseEntity handleRuntimeException()  {
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Unexpected error occurred");
}

This caused test failures, because the handler probably catches also the exceptions thrown by spring security while protecting endpoints, there is status code 500 (from handler) instead of 401 or 403 expected (from spring)

I have tried to use custom handler like:

.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())

It is still caught by the @ExceptionHandler with RuntimeException clause.

My questions are:

  1. What are the exceptions thrown by Spring Security (if I know it I can handle them separately)
  2. Is the idea of catching the exceptions separately correct for the problem, maybe there is another solution, that would do what I want to do in a better way?

Spring security is based on filter, and Spring MVC is based on servlet, The @ExceptionHandler should not catch the exception that spring security throws.

.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())

This is an ExceptionTranslationFilter , it will handle the AuthenticationException and AccessDeniedException , source code see here

            Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
            RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
            if (ase == null) {
                ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
            }

            if (ase == null) {
                if (var10 instanceof ServletException) {
                    throw (ServletException)var10;
                }

                if (var10 instanceof RuntimeException) {
                    throw (RuntimeException)var10;
                }

                throw new RuntimeException(var10);
            }

            if (response.isCommitted()) {
                throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var10);
            }

            this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);

AuthenticationException (usually means not authenticated) will be handled by AuthenticationEntryPoint , you can implement your own AuthenticationEntryPoint , just overwrite the commence method, eg HttpStatusEntryPoint , it just return response with customer status.

public final class HttpStatusEntryPoint implements AuthenticationEntryPoint {
    private final HttpStatus httpStatus;

    public HttpStatusEntryPoint(HttpStatus httpStatus) {
        Assert.notNull(httpStatus, "httpStatus cannot be null");
        this.httpStatus = httpStatus;
    }

    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setStatus(this.httpStatus.value());
    }
}

and you can set the entry point to the exception handling.

exceptionHandling()
    .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))

AccessDeniedHandler (usually means not authorized) will be handled by AccessDeniedHandler which you have done.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM