Spring Rest OAuth2 AuthorizationServer: Deactivate user after n failed login attempts

Viewed 444

I use @EnableAuthorizationServer to get an OAuth2 AuthorizationServer for my REST API. It works. Now I want to disable user accounts after 10 failed login attempts.

I tried to configure a custom AuthenticationManager, but it's not being called.

This is my AuthorizationServerConfigurerAdapter:

@Configuration
@EnableAuthorizationServer
@EnableResourceServer
public class OAuth2AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

  @Autowired
  private AuthenticationClientDetailsService clientDetailsService;

  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
  }

  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(clientDetailsService());
  }

  private ClientDetailsService clientDetailsService() {
    return clientDetailsService;
  }

  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancer()).authenticationManager(authenticationManager());
  }

  @Bean
  public TokenStore tokenStore() {
    return new InMemoryTokenStore();
  }

  @Bean
  public TokenEnhancer tokenEnhancer() {
    return new CustomTokenEnhancer();
  }

  @Bean
  public AuthenticationManager authenticationManager() {
    return new CustomAuthenticationManager();
  }
}

And this my dummy AuthenticationManager:

@Log4j
public class CustomAuthenticationManager implements AuthenticationManager {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        log.error("I woz called!");
        authentication.setAuthenticated(true);
        return authentication;
    }

}

I would expect CustomAuthenticationManager.authenticate() to be called when I request a token, so I could check the credentials and lock the account accordingly, but the method is never called :-(

1 Answers

The only way I got this to work is with a custom TokenEndpointAuthenticationFilter:

AuthenticationManagerProvider

This is necessary because you need an AuthenticationManager to instantiate a TokenEndpointAuthenticationFilter and you want the one Spring has instantiated automatically.

@Configuration // @Order(99) is important so this bean is initialized before the
@Order(99)     // AuthorizationServerConfigurer at 100           
public class AuthenticationManagerProvider extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

AuthorizationServerConfigurerAdapter

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    AuthenticationManager authenticationManager = authenticationManagerProvider.authenticationManagerBean();
    DefaultOAuth2RequestFactory oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
    CustomAuthenticationFilter filter = new CustomAuthenticationFilter(authenticationManager, oAuth2RequestFactory);

    security.tokenKeyAccess("permitAll()") //
        .checkTokenAccess("isAuthenticated()") //
        .allowFormAuthenticationForClients() //
        .addTokenEndpointAuthenticationFilter(filter);
}

CustomAuthenticationFilter

You probably could specifically override successful and unsuccessful authentication attempts, but in my case I need to handle both to reset the number of failed attempts on a successful one and lock the account when the max is reached, so I decided to override doFilter() directly.

Just remember to call the super implementation at the end or your client won't get a token!

public class CustomAuthenticationFilter extends TokenEndpointAuthenticationFilter {

    public CustomAuthenticationFilter(AuthenticationManager authenticationManager, OAuth2RequestFactory oAuth2RequestFactory) {
        super(authenticationManager, oAuth2RequestFactory);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // do your thing, then call super-implementation to continue normal authentication flow
        super.doFilter(request, response, chain);
    }
}
Related