0

I'm implementing a custom authentication provider in my app. In my provider, I throw different exceptions with different messages depending on the situation. Please see my code:

@Component
public class MyLdapAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        // Connect to LDAP - throw ConnectionException if the LDAP server is unreachable

        // Authenticate

        // Worng username or password, throw BadCredentialsException("Incorrect username or password.")

        // Not enough right to use my app, throw BadCredentialsException("This user is not allowed to use the service.");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

To catch those exceptions, I implemented a CustomAuthenticationEntryPoint, like this:

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final HandlerExceptionResolver resolver;

    @Autowired
    public CustomAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
        this.resolver = resolver;
    }

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) {
        resolver.resolveException(httpServletRequest, httpServletResponse, null, e);
    }
}

As you can see, I resolve the exception so that I can catch it again in my @RestControllerAdvice (I want to centralize the exception handling).

My problem is that the commence method of the CustomAuthenticationEntryPoint turns all exceptions into AuthenticationException. No matter what exception I throw in my authentication provider, what I get is always an authentication exception with a fixed message:

Full authentication is required to access this resource

In conclusion, I can catch exceptions thrown from the AuthenticationProvider, but not the correct one.

My question: How can I catch the correct exceptions thrown from the AuthenticationProvider?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Triet Doan
  • 11,455
  • 8
  • 36
  • 69
  • No, my question is: how can I catch correct exceptions thrown from the `AuthenticationProvider`? As you can see, I have no way to catch the `ConnectionException` or `BadCredentialsException`. – Triet Doan Aug 23 '19 at 11:56
  • I checked them already. They just want to catch `AuthenticationException`. For me, it's different. I want to catch the specific exception to know the cause of the authentication failure (cannot connect to the authentication source, wrong credentials,...). This information is not available in `AuthenticationException`. – Triet Doan Aug 23 '19 at 12:23
  • In your last comment your wrote that your question is not about the type of exception, it is about catching exception. Hence, I deleted my last comment. Now it is again about the type of exception. If you read my last comment you would already know the answer of your question. – dur Aug 23 '19 at 12:27
  • Your question: *No, my question is: how can I catch correct exceptions thrown from the AuthenticationProvider?* Is answered in the other questions. – dur Aug 23 '19 at 12:28
  • *As you can see, I have no way to catch the ConnectionException or BadCredentialsException.* That question was answered in my comment. – dur Aug 23 '19 at 12:28
  • Sorry, maybe I confuse you. When I say **correct** exception, I meant the specific one (like `ConnectionException` or `BadCredentialsException`), not only the `AuthenticationException`. You said that my question was answered in other questions and in your comment, but I don't see it. Could you please post your idea here? Or the specific link to the answer you're refering to? – Triet Doan Aug 23 '19 at 12:41
  • I wrote (in the deleted comment) that Spring Security is converting all exceptions to one general exception for security reasons. I also wrote that AFAIK it is possible to change that default (but it is not recommended). If you really want that security risk, I could search for that configuration. – dur Aug 23 '19 at 12:44
  • Ah okay, thank you. I don't want to expose to security risks. Maybe adding a filter to catch the exception as suggested by @BogdanSucaciu in his answer below is a better idea. – Triet Doan Aug 23 '19 at 12:51
  • BogdanSucaciu's answered the question I marked as duplicate. He didn't answer the question about the type of the exception. You still would have a security issue if you use BogdanSucaciu's answer, because his answer exposes that information (in his `if` statements). – dur Aug 23 '19 at 12:54
  • Maybe a bit off-topic, but could you tell me why exposing exception types is considered as a security issue? – Triet Doan Aug 23 '19 at 13:01
  • AFAIK the attacker should not get any information about the failing of an authentication. You don't tell him things like account is locked, account is deleted, username not exists, password wrong, ... The attacke would know that some account exists. – dur Aug 23 '19 at 13:15
  • Other exceptions could give the attacker other informations. However, exceptions like `ConnectionException` are useless, because the user cannot change it, it is not in his hand. – dur Aug 23 '19 at 13:18
  • I see your points. Thank you for your help! :) – Triet Doan Aug 23 '19 at 13:40

1 Answers1

1

The way that I solved it was by adding a new Filter before the CorsFilter:

On my SecurityConfigurer class:

@Override
protected void configure(HttpSecurity http) throws Exception {

    http
        .addFilterBefore(new ExceptionFilter(exceptionHandler), CorsFilter.class)
        .....
}

ExceptionFiler class:

public class ExceptionFilter extends OncePerRequestFilter {

private HandlerExceptionResolver resolver;

public ExceptionFilter(HandlerExceptionResolver resolver) {
    this.resolver = resolver;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        filterChain.doFilter(request, response);
    } catch (Exception ex) {
        LOG.error("An exception has been thrown by one of the filters: ", ex);
                    ResponseEntity<Object> error;
        if (ex instanceof ConnectionException) {
            error = resolver.handleConnectionException(ex);
        } else if (ex instanceof BadCredentialsException) {
            error = resolver.handleBadCredentialsException(ex);
        } else {
            error = resolver.handleGenericException(ex);
        }

        response.setStatus(error.getStatusCode().value());
        response.getWriter().write(error.getBody().toString());
    }
}

}

You can play around with if statements, catch clauses, etc. but you've got the idea.

BogdanSucaciu
  • 884
  • 6
  • 13
  • 1
    Mine is a `ResponseEntityExceptionHandler` ( `org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler` ). I guess it doesn't really matter as long as you're passing the exception to the handler... the trick is to actually catch the exception in the filter so you can do anything you want with it in the catch clause – BogdanSucaciu Aug 23 '19 at 12:28
  • @BogdanSucaciu Sure, it doesn't matter, but it is distracting user's with few knowledge about Spring. I would recommend to change your answer to make the code working, using only the method `resolveException` from the interface. – dur Aug 23 '19 at 12:33
  • And may I ask, why you choose to add this `ExceptionFilter` before the `CorsFilter`? Any specific reason? – Triet Doan Aug 23 '19 at 12:46
  • I have defined a custom CSRF filter which throws some custom exceptions. So, I wanted my `ExceptionFilter` to catch exceptions thrown by the `CsrfFilter` as well. In your scenario, I guess it would be ok to place it before `UsernamePasswordAuthenticationFilter`. – BogdanSucaciu Aug 23 '19 at 13:23
  • handleConnectionException is not exist in spring boot 3.3.5 – aswzen Aug 27 '20 at 05:21