Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot Security does not throw 401 Unauthorized Exception but 404 Not Found

My authentication is based on a spring-boot-security-example. When I enter an invalid token, I would like to throw a 401 Unauthorized exception. However, I always get a 404 resource not found instead. My configuration sets an exception handling but it is ignored - probably because my AuthenticationFilter is added before and the request does not reach my exception handler.

What would I need to change to throw 401 exceptions instead?

I have a authentication filter:

public class AuthenticationFilter extends GenericFilterBean {

...

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = asHttp(request);
    HttpServletResponse httpResponse = asHttp(response);
    Optional<String> token = Optional.fromNullable(httpRequest.getHeader("X-Auth-Token"));

    try {
        if (token.isPresent()) {
            logger.debug("Trying to authenticate user by X-Auth-Token method. Token: {}", token);
            processTokenAuthentication(token);
            addSessionContextToLogging();
        }

        logger.debug("AuthenticationFilter is passing request down the filter chain");
        chain.doFilter(request, response);
    } catch (InternalAuthenticationServiceException internalAuthenticationServiceException) {
        SecurityContextHolder.clearContext();
        logger.error("Internal authentication service exception", internalAuthenticationServiceException);
        httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    } catch (AuthenticationException authenticationException) {
        SecurityContextHolder.clearContext();
        httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
    } finally {
        MDC.remove(TOKEN_SESSION_KEY);
        MDC.remove(USER_SESSION_KEY);
    }
}

private void addSessionContextToLogging() {
    ...
}

...

private void processTokenAuthentication(Optional<String> token) {
    Authentication resultOfAuthentication = tryToAuthenticateWithToken(token);
    SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
}

private Authentication tryToAuthenticateWithToken(Optional<String> token) {
    PreAuthenticatedAuthenticationToken requestAuthentication = new PreAuthenticatedAuthenticationToken(token, null);
    return tryToAuthenticate(requestAuthentication);
}

private Authentication tryToAuthenticate(Authentication requestAuthentication) {
    Authentication responseAuthentication = authenticationManager.authenticate(requestAuthentication);
    if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
        throw new InternalAuthenticationServiceException("Unable to authenticate Domain User for provided credentials");
    }
    logger.debug("User successfully authenticated");
    return responseAuthentication;
}

a AuthenticationProvider implementation:

@Provider
public class TokenAuthenticationProvider implements AuthenticationProvider {

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Optional<String> token = (Optional) authentication.getPrincipal();
    if (!token.isPresent() || token.get().isEmpty()) {
        throw new BadCredentialsException("No token set.");
    }
    if (!myCheckHere()){
        throw new BadCredentialsException("Invalid token");
    }

    return new PreAuthenticatedAuthenticationToken(myConsumerObject, null, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_API_USER"));
}

...

}

and a configuration which looks as follows:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.
            csrf().disable().
            sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
            and().
            anonymous().disable().
            exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint());

    http.addFilterBefore(new AuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
}


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(tokenAuthenticationProvider());
}


@Bean
public AuthenticationProvider tokenAuthenticationProvider() {
    return new TokenAuthenticationProvider();
}

@Bean
public AuthenticationEntryPoint unauthorizedEntryPoint() {
    return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
like image 693
Frame91 Avatar asked Jan 12 '16 20:01

Frame91


People also ask

How to handle Spring Security exception?

To handle REST exception, we generally use @ControllerAdvice and @ExceptionHandler in Spring MVC but these handler works if the request is handled by the DispatcherServlet. However, security-related exceptions occur before that as it is thrown by Filters.

How to handle custom exception in Spring Boot?

The @ExceptionHandler is an annotation used to handle the specific exceptions and sending the custom responses to the client. Define a class that extends the RuntimeException class. You can define the @ExceptionHandler method to handle the exceptions as shown.

How do you throw an unauthorized exception?

An UnauthorizedAccessException exception is typically thrown by a method that wraps a Windows API call. To find the reasons for the exception, examine the text of the exception object's Message property. UnauthorizedAccessException uses the HRESULT COR_E_UNAUTHORIZEDACCESS , which has the value 0x80070005.

How do I send a 401k response in Java?

For error status codes like 401, use the more specific sendError(): httpResponse. sendError(HttpServletResponse. SC_UNAUTHORIZED, "your message goes here");


2 Answers

I found the answer in this thread: Return HTTP Error 401 Code & Skip Filter Chains

Instead of

httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());

I need to call

httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

It seems like the chain will stop when I don't continue calling it and by setting the status to a different code - the exception is thrown correctly

like image 198
Frame91 Avatar answered Sep 16 '22 12:09

Frame91


I solved it by adding the following annotation on my top-level @SpringBootApplication class:

@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})

Could Spring Boot have trouble finding its default error page?

like image 45
candide Avatar answered Sep 18 '22 12:09

candide