Spring-security-oauth: "UserDetailsService is required" but I'm using an AuthenticationProvider

Created on 30 Jul 2016  路  13Comments  路  Source: spring-projects/spring-security-oauth

I'm implementing an OAuth2 server with JWT tokens, but instead of use a UserDetailsService implementation, I'm using an AuthenticationProvider implementation because I delegate the authorization to another service passing the user and the password. The access_token works Ok, but when I try to use the refresh_token to get another token, I get this error.

curl -u trusted:secret http://localhost:5000/oauth/token -d grant_type=refresh_token -d refresh_token=<refresh_token>
{"error":"server_error","error_description":"UserDetailsService is required."}

And this is logged:

01:49:42.483  INFO TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.

Looking this I downgrade the version of spring-security-oauth2 to 2.0.6.RELEASE and works, but it works because the AuthenticationProvider class is never called to check if the user is still active.

How can I setup Spring Security to use the provider in the refresh token workflow to check the user before deliver the new acces_token? Can I use the latest version of the oauth2 library in some way for this? This is a bug or I have to fix something?

_Environment tested_:

  • Spring Boot tested with versions 1.3.2, 1.3.7, 1.4.0
  • spring-security-oauth2 2.0.6 and 2.0.10
  • spring-security-jwt 1.0.4

Most helpful comment

@EmilIfrim sorry for the late reply, I just added an example repo here:

https://github.com/gdong42/spring-auth-example

All 13 comments

I think I understand what was the problem, and I solved in that way.

Using an AuthenticationProvider implementation will be incorrect, and almost impossible to refresh a token with a refresh_token, because in that kind of implementation you have to resolve if the password is correct, but the refresh token doesn't have that information. So, if you want an OAuth server with just tokens authentication, an AuthenticationProvider implementation is enough, but if you want also the refresh token mechanism working, you have to implement a UserDetailsService, because this implementation is agnostic of the password.

This is the way I configured both classes the provider and the user details services, with the latest versions of Spring Boot, OAuth2 and JWT libraries:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .authenticationManager(multitenancyAuthenticationProvider)
            .userDetailsService(multitenancyUserDetailsService)

            // Use JwtTokenStore and our jwtAccessTokenConverter
            .tokenStore(tokenStore())
            .accessTokenConverter(tokenEnhancer())
        ;
    }
}

The multitenancyAuthenticationProvider object implement the classes AuthenticationProvider and AuthenticationManager (both classes have the same method to implement, I don't know the difference).

I'm facing exactly the same problem. I have multiple AuthenticationProvider which look up from different users store to authenticate. Now how am I supposed to supply a UserDetailsService for multiple authentication provider?

@hotterd How are you providing multiple AuthenticationProvider classes? Could you provide the code to do that?

@mrsarm I add them through WebSecurityConfig:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private FooAuthenticationProvider fooAuthenticationProvider;

  @Autowired
  private BarAuthenticationProvider barAuthenticationProvider;

...

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

In FooAuthenticationProvider and BarAuthenticationProvider, different authentication data sources are provided in authentication method, and supports() method of the the provider checks if the given authentication request should be handled by that provider.

@mrsarm Found another similar open issue https://github.com/spring-projects/spring-security-oauth/issues/685, I'll use the hack proposed in the comments and wait for the official fix.

@hotterd thanks for the above posts.

Yes, I'm implementing a hack similar to #685, putting all the user retrievement in one provider and in one user details service, but with a twist: each user source has a different client_id, and that id is provided in the request with the client secret in the Authorization header, same with try to refresh a token, so, in the user detail service and in the authentication provider you can check that client_id to know against which datasource you have to authenticate.

To know from the user details or the provider class what client_id is accessing to your system, you only have to this:

SecurityContextHolder.getContext().getAuthentication()

@mrsarm Thanks for the tips! The SecurityContextHolder reminder is quite helpful, I also faced the problem how to get the token authentication request, and was thinking to query the two authentication method in a fixed order. Your suggestion saves a lot useless roundtrips.

We are actually on the same path, I'm also using client_id to identify which authentication method to use, except that I'm using two different AuthorizationProviders and two UsernamePasswordAuthenticationToken subclasses to identify which to use. It's a bit hacky, but works for me.

@hotterd Can you post example of code where AuthenticationProviders/UsernamePasswordAuthenticationToken are created based on client_id ?

@EmilIfrim sorry for the late reply, I just added an example repo here:

https://github.com/gdong42/spring-auth-example

I am in a very similar case. I have implemented an CustomAuthenticationProvider and need the DetailService for refresh_token workflow.

Is there any solution for avoid implementing the DetailService? If not, how can I secure the DetailService as it's only check for the username of the logged user?

@gdong42 Thank you for the example. Nice and clean. It has though few limitations

  1. It does not scale when clients are dynamic (added through configuration, e.g in database)
  2. The user details service relies only on username

@ffradegrada The framework has this limitation and I couldn't find a way to avoid implementing the UserDetailsService. I found a way to bypass the username check:

  1. When authentication is performed I use my own implementation of a Principal (I add a unique ID of the user from the database)
  2. The refresh token process needs the user details in the Preauthentication phase. Therefor I implemented AuthenticationUserDetailsService which passes the PreAuthenticatedAuthenticationToken. From this token I can get the Principal and extract my unique ID:
    `@Service("customPreAuthenticationUserDetailsService")
    public class CustomPreAuthenticationUserDetailsService implements RatorAuthenticationUserDetailsService {

    @Override
    public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) throws UsernameNotFoundException {

    UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) token.getPrincipal();
    CustomActor authenticatedCustomActor = (CustomActor )authenticationToken.getPrincipal();

    CustomActorDetailsService actorDetailsService = new CustomActorDetailsService();
    LoginResult result = actorDetailsService.getActorById(authenticatedCustomActor.getActor().getId());
    CustomActor customActor = result.getActor();

    CustomrActor userActor = result.getActor();

    if (userActor != null){
    return new org.springframework.security.core.userdetails.User(token.getName(), "N/A", getAuthorities(userActor.getRoles()));
    }

    throw new UsernameNotFoundException(
    "User " + token.getName() + " was not found in the database or not active anymore");
    }

    @Override
    public boolean supports(PreAuthenticatedAuthenticationToken token) {
    if (token == null || token.getPrincipal() == null || !UsernamePasswordAuthenticationToken.class.isAssignableFrom(token.getPrincipal().getClass())){
    return false;
    }

    UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) token.getPrincipal();

    return authenticationToken.getPrincipal() != null && CustomActor.class.isAssignableFrom(authenticationToken.getPrincipal().getClass());
    }

    private Collection getAuthorities(Collection roles){
    Function roleConverter = (role) -> new SimpleGrantedAuthority(role);
    return roles.stream().map(roleConverter).collect(Collectors.toList());
    }
    }`

@EmilIfrim to scale you can use @mrsarm 's implementation and use a single MultitenancyAuthenticationProvider, then do the dynamic work in the provider.

To configure the provider, I assume you can probably use a ClientDetailsServiceConfigurer.jdbc() builder in the OAuth2Config configuration class, to validate against dynamic clients using a jdbc datasource.

@mrsarm

Can u please show some sample code how u implement MultitenancyAuthenticationProvider.
I have 2 Authentication provider, 1 from DB and other from Ldap.

Was this page helpful?
0 / 5 - 0 ratings