Add custom UserDetailsService to Spring Security OAuth2 app

后端 未结 4 1965
囚心锁ツ
囚心锁ツ 2020-12-16 16:57

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

The default user with default password is def

相关标签:
4条回答
  • 2020-12-16 17:11

    For anyone got UserDetailsService is required error when doing refresh token, and you confirm you already have UserDetailsService bean.

    try add this:

    @Configuration
    public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter {
        private UserDetailsService userDetailsService;
    
        public GlobalSecurityConfig(UserDetailsService userDetailsService) {
            this.userDetailsService = userDetailsService;
        }
    
        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
        }
    }
    

    This is my try and error, maybe not work for you.

    By the way, if you give up "guess" which bean will pick by spring, you can extends AuthorizationServerConfigurerAdapter and WebSecurityConfigurerAdapter and config all stuff by yourself, but I think it's lose power of spring autoconfig. Why I need config everything if I just need customize some config?

    0 讨论(0)
  • 2020-12-16 17:24

    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.
    0 讨论(0)
  • 2020-12-16 17:25

    My requirement was to get a database object off the back of the oauth2 email attribute. I found this question as I assumed that I need to create a custom user details service. Actually I need to implment the OidcUser interface and hook into that process.

    Initially I thought it was the OAuth2UserService, but I've set up my AWS Cognito authentication provider so that it's open id connect..

    //inside WebSecurityConfigurerAdapter
    
    http
    .oauth2Login()
    .userInfoEndpoint()
    .oidcUserService(new CustomOidcUserServiceImpl());
    
    ...
    
    public class CustomOidcUserServiceImpl implements OAuth2UserService<OidcUserRequest, OidcUser> {
    
        private OidcUserService oidcUserService = new OidcUserService();
    
        @Override
        public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
            OidcUser oidcUser = oidcUserService.loadUser(userRequest);
            return new CustomUserPrincipal(oidcUser);
        }
    }
    
    ...
    
    public class CustomUserPrincipal implements OidcUser {
    
        private OidcUser oidcUser;
    
        //forward all calls onto the included oidcUser
    }
    

    The custom service is where any bespoke logic can go. I plan on implementing UserDetails interface on my CustomUserPrincipal so that I can have different authentication mechanisms for live and test to facilitate automated ui testing.

    0 讨论(0)
  • 2020-12-16 17:29

    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.

    0 讨论(0)
提交回复
热议问题