Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AuthenticationEntryPoint only sometimes called

I have a simple AuthenticationEntryPoint which should set the WWW-Authenticate header for unauthorized requests.

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {
        response.setHeader("WWW-Authenticate", "FormBased");
        response.sendError(401, authException.getMessage());
    }
}

I use it in one of the configure methods of AuthorizationServerConfigurer

@Override
public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
    authorizationServerSecurityConfigurer.authenticationEntryPoint(authenticationEntryPoint);
}

This commence method is not always called, though. It gets called when there is no Authorize header in the request or when the Authorize header value doesn't start with 'Basic'. However, if the Authorize header starts with 'Basic', the commence method is not called (and the value of the response is Basic realm="oauth2/client"). How can I ensure that this method gets called?

like image 667
cscan Avatar asked Feb 08 '23 01:02

cscan


2 Answers

As pointed out by AliDehghani this occurs because the BasicAuthenticationFilter uses a BasicApplicationEntryPoint regardless of the ApplicationEntryPoint declared in AuthorizationServerSecurityConfigurer. In order to get BasicAuthenticationFilter to use my CustomApplicationEntryPoint I needed to create a new CustomBasicAuthenticationFilter and add the @Autowire annotation to the constructor:

@Component
public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter {

    @Autowired
    public CustomBasicAuthenticationFilter(AuthenticationManager authenticationManager,
                                     AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }
}

This is then added to one of the configure methods of AuthorizationServerConfigurer

@Override
public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
    authorizationServerSecurityConfigurer
            .authenticationEntryPoint(authenticationEntryPoint)
            .addTokenEndpointAuthenticationFilter(customBasicAuthenticationFilter);
}

Now the application uses my CustomBasicAuthenticationFilter - which is functionally equivalent to the BasicAuthenticationFilter. However, it now includes the declared AuthenticationEntryPoint bean during construction - which is my CustomAuthenticationEntryPoint.

like image 78
cscan Avatar answered Feb 13 '23 22:02

cscan


In order to get an Access Token, you should authenticate your client through HTTP Basic:

Authorization: Basic Base64(client_id:client_secret)

This commence method is not always called, though. It gets called when there is no Authorize header in the request or when the Authorize header value doesn't start with 'Basic'. However, if the Authorize header starts with 'Basic', the commence method is not called

Spring Security maintains a filter chain internally where each of the filters has a particular responsibility and one of them is BasicAuthenticationFilter which would process Basic Authentications. If you take a peek at its doFilterInteral mthod, you would see:

if (header == null || !header.startsWith("Basic ")) {
    chain.doFilter(request, response);
    return;
}

If you do not pass an Authorization header or your Authorization header does not start with Basic, it would skip the current filter for other filters in the security filter chain. Eventually it will throw an instance of AuthenticationException which will be caught by ExceptionTranslationFilter and ExceptionTranslationFilter would call your registered AuthenticationEntryPoint.

But when you pass a Basic Authorization header, the BasicAuthenticationFilter itself would process the authentication tokens. If the passed credentials were invalid, the BasicAuthenticationFilter would catch the exception itself and call the BasicAuthenticationEntryPoint, not your AuthenticationEntryPoint:

catch (AuthenticationException failed) {
    SecurityContextHolder.clearContext();

    if (debug) {
        this.logger.debug("Authentication request for failed: " + failed);
    }

    this.rememberMeServices.loginFail(request, response);

    onUnsuccessfulAuthentication(request, response, failed);

    if (this.ignoreFailure) {
        chain.doFilter(request, response);
    }
    else {
        this.authenticationEntryPoint.commence(request, response, failed);
    }

    return;
}
like image 45
Ali Dehghani Avatar answered Feb 14 '23 00:02

Ali Dehghani