Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add custom UserDetailsService to Spring Security OAuth2 app

How do I add the custom UserDetailsService below to this Spring OAuth2 sample?

The default user with default password is defined in the application.properties file of the authserver app.

However, I would like to add the following custom UserDetailsService to the demo package of the authserver app for testing purposes:

package demo;

import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Service;

@Service
class Users implements UserDetailsManager {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password;
        List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
        if (username.equals("Samwise")) {
            auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT");
            password = "TheShire";
        }
        else if (username.equals("Frodo")){
            auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT");
            password = "MyRing";
        }
        else{throw new UsernameNotFoundException("Username was not found. ");}
        return new org.springframework.security.core.userdetails.User(username, password, auth);
    }

    @Override
    public void createUser(UserDetails user) {// TODO Auto-generated method stub
    }

    @Override
    public void updateUser(UserDetails user) {// TODO Auto-generated method stub
    }

    @Override
    public void deleteUser(String username) {// TODO Auto-generated method stub
    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {
        // TODO Auto-generated method stub
    }

    @Override
    public boolean userExists(String username) {
        // TODO Auto-generated method stub
        return false;
    }
}

As you can see, this UserDetailsService is not autowired yet, and it purposely uses insecure passwords because it is only designed for testing purposes.

What specific changes need to be made to the GitHub sample app so that a user can login as username=Samwise with password=TheShire, or as username=Frodo with password=MyRing? Do changes need to be made to AuthserverApplication.java or elsewhere?


SUGGESTIONS:


The Spring OAuth2 Developer Guide says to use a GlobalAuthenticationManagerConfigurer to configure a UserDetailsService globally. However, googling those names produces less than helpful results.

Also, a different app that uses internal spring security INSTEAD OF OAuth uses the following syntax to hook up the UserDetailsService, but I am not sure how to adjust its syntax to the current OP:

@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {

    @Autowired
    private Users users;

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(users);
    }
}

I tried using @Autowire inside the OAuth2AuthorizationConfig to connect Users to the AuthorizationServerEndpointsConfigurer as follows:

@Autowired//THIS IS A TEST
private Users users;//THIS IS A TEST

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
   endpoints.authenticationManager(authenticationManager)
        .accessTokenConverter(jwtAccessTokenConverter())
        .userDetailsService(users)//DetailsService)//THIS LINE IS A TEST
        ;
}

But the Spring Boot logs indicate that the user Samwise was not found, which means that the UserDetailsService was not successfully hooked up. Here is the relevant excerpt from the Spring Boot logs:

2016-04-20 15:34:39.998 DEBUG 5535 --- [nio-9999-exec-9] o.s.s.a.dao.DaoAuthenticationProvider    :  
        User 'Samwise' not found
2016-04-20 15:34:39.998 DEBUG 5535 --- [nio-9999-exec-9]   
        w.a.UsernamePasswordAuthenticationFilter :  
        Authentication request failed:  
        org.springframework.security.authentication.BadCredentialsException:  
        Bad credentials

What else can I try?

like image 586
CodeMed Avatar asked Apr 19 '16 22:04

CodeMed


People also ask

What is the use of UserDetailsService in Spring Security?

UserDetailsService is used by DaoAuthenticationProvider for retrieving a username, password, and other attributes for authenticating with a username and password. Spring Security provides in-memory and JDBC implementations of UserDetailsService .

What is AuthenticationProvider in Spring Security?

The Authentication Provider Spring Security provides a variety of options for performing authentication. These options follow a simple contract; an Authentication request is processed by an AuthenticationProvider, and a fully authenticated object with full credentials is returned.

Is spring boot OAuth2 deprecated?

End of Life NoticeThe Spring Security OAuth project has reached end of life and is no longer actively maintained by VMware, Inc. This project has been replaced by the OAuth2 support provided by Spring Security and Spring Authorization Server.


2 Answers

I ran into a similar issue when developing my oauth server with Spring Security. My situation was slightly different, as I wanted to add a UserDetailsService to authenticate refresh tokens, but I think my solution will help you as well.

Like you, I first tried specifying the UserDetailsService using the AuthorizationServerEndpointsConfigurer, but this does not work. I'm not sure if this is a bug or by design, but the UserDetailsService needs to be set in the AuthenticationManager in order for the various oauth2 classes to find it. This worked for me:

@Configuration
@EnableWebSecurity 
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  Users userDetailsService;

  @Autowired
  public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
    // other stuff to configure your security
  }

}

I think if you changed the following starting at line 73, it may work for you:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.parentAuthenticationManager(authenticationManager)
        .userDetailsService(userDetailsService);
}

You would also of course need to add @Autowired Users userDetailsService; somewhere in WebSecurityConfigurerAdapter

Other things I wanted to mention:

  1. This may be version specific, I'm on spring-security-oauth2 2.0.12
  2. I can't cite any sources for why this is the way it is, I'm not even sure if my solution is a real solution or a hack.
  3. The GlobalAuthenticationManagerConfigurer referred to in the guide is almost certainly a typo, I can't find that string anywhere in the source code for anything in Spring.
like image 166
Manan Mehta Avatar answered Sep 19 '22 17:09

Manan Mehta


I ran into the same issue and originally had the same solution as Manan Mehta posted. Just recently, some version combination of spring security and spring oauth2 resulted in any attempt to refresh tokens resulting in an HTTP 500 error stating that UserDetailsService is required in my logs.

The relevant stack trace looks like:

java.lang.IllegalStateException: UserDetailsService is required.
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:463)
at org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper.loadUserDetails(UserDetailsByNameServiceWrapper.java:68)
at org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider.authenticate(PreAuthenticatedAuthenticationProvider.java:103)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.refreshAccessToken(DefaultTokenServices.java:150)

You can see at the bottom that the DefaultTokenServices is attempting to refresh the token. It then calls into an AuthenticationManager to re-authenticate (in case the user revoked permission or the user was deleted, etc.) but this is where it all unravels. You see at the top of the stack trace that UserDetailsServiceDelegator is what gets the call to loadUserByUsername instead of my beautiful UserDetailsService. Even though inside my WebSecurityConfigurerAdapter I set the UserDetailsService, there are two other WebSecurityConfigurerAdapters. One for the ResourceServerConfiguration and one for the AuthorizationServerSecurityConfiguration and those configurations never get the UserDetailsService that I set.

In tracing all the way through Spring Security to piece together what is going on, I found that there is a "local" AuthenticationManagerBuilder and a "global" AuthenticationManagerBuilder and we need to set it on the global version in order to have this information passed to these other builder contexts.

So, the solution I came up with was to get the "global" version in the same way the other contexts were getting the global version. Inside my WebSecurityConfigurerAdapter I had the following:

@Autowired
public void setApplicationContext(ApplicationContext context) {
    super.setApplicationContext(context);
    AuthenticationManagerBuilder globalAuthBuilder = context
            .getBean(AuthenticationManagerBuilder.class);
    try {
        globalAuthBuilder.userDetailsService(userDetailsService);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

And this worked. Other contexts now had my UserDetailsService. I leave this here for any brave soldiers who stumble upon this minefield in the future.

like image 20
Michael Ressler Avatar answered Sep 18 '22 17:09

Michael Ressler