10
votes

Context

I'm developing an application that allows an authenticated user to create OAuth2 bearer tokens for use with APIs published by the organization. The idea is to allow the user to self generate / revoke such tokens, similar to GitHub's Personal API tokens. The user can then use the issued tokens to gain programmatic access to protected resources. In this configuration, the OAuth "Client", "Authorization Server" and the "Resource Server" belong to the organization. For now, all of these services reside in the same process.

To this end, I'm trying support the Resource Owner Password Credentials Grant type. The implementation environment consists of following:

  • Spring Boot
  • Spring Security OAuth2

One constraint the implementation has is that is does not have access to the stored password. This processing is delegated to an internal web service which does the actual authentication.

Problem

Because of the constraint of not having access to the stored password, the default configured DaoAuthenticationProvider cannot be used since it requires access to the password provided by the UserDetails object returned by the provider's UserDetailsService.

My guess is that I will need to replace this AuthenticationProvider with a custom implementation. However, all attempts at doing so seem to not take effect.

The following seemed to register correctly in the parent reference of the AuthenticationManager, but is not delegated to at runtime (due the DaoAuthenticationProvider taking precedence):

@Configuration
public class SecurityConfig extends GlobalAuthenticationConfigurerAdapter {

  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(new AuthenticationProvider() {

      @Override
      public boolean supports(Class<?> authentication) {
        // For testing this is easier, but should check for UsernamePasswordAuthentication.class
        return true;
      }

      @Override
      public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // Perform custom logic...
        return authentication;
      }

    });
  }

}

It seems no matter what I try (see references below) I always get the following two providers in ProviderManager when the BasicAuthenticationFilter invokes Authentication authResult = authenticationManager.authenticate(authRequest) in its doFilter method:

[ org.springframework.security.authentication.AnonymousAuthenticationProvider@366815e4, org.springframework.security.authentication.dao.DaoAuthenticationProvider@5da3e311 ]

I believe this may be a consequence of the AuthorizationServerSecurityConfigurer's clientCredentialsTokenEndpointFilter method. However, this class is marked final and therefore cannot be customized.

Any suggestions or pointers would be greatly appreciated.

Resources

Things that I have tried / researched:

2
Any luck here? I'm running into the same issue: no matter WHAT I do, the BasicAuthenticationFilter always gets a org.springframework.security.authentication.dao.DaoAuthenticationProvider instance - which then fails to find the user supplied via HttpBasic.demaniak

2 Answers

5
votes

If I understand correctly, you need a custom authentication manager for users in the password grant. There is a builder method for that: AuthorizationServerEndpointsConfigurer.authenticationManager(AuthenticationManager). And an example of using it:

@Configuration
@EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

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

    ...

(from https://github.com/spring-projects/spring-security-oauth/blob/master/tests/annotation/vanilla/src/main/java/demo/Application.java#L42).

1
votes

As mentioned in the comments, for the following snippet to autowire the 'global' AuthenticationManager you must make it somehow known to the context.

The snippet:

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

configures only that specific instance of HttpSecurity.

I personally prefer to set the bean as primary and also set the order of my central configuration to 1 to force this config to take precedence over the other (auto) configurations.

@Order(1)
@Configuration
public class SecurityConfig extends GlobalAuthenticationConfigurerAdapter {

    @Bean
    @Primary
    public AuthenticationProvider authenticationProvider() {
        return new AuthenticationProvider() {

            @Override
            public boolean supports(Class<?> authentication) {
                // For testing this is easier, but should check for
                // UsernamePasswordAuthentication.class
                return true;
            }

            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                // Perform custom logic...
                return authentication;
            }

        };
    }
}