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?
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
.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With