【问题标题】:Add custom UserDetailsService to Spring Security OAuth2 app将自定义 UserDetailsS​​ervice 添加到 Spring Security OAuth2 应用程序
【发布时间】:2016-08-12 08:53:33
【问题描述】:

如何将下面的自定义UserDetailsService 添加到this Spring OAuth2 sample

默认user 和默认passwordauthserver 应用程序的application.properties 文件中定义。

但是,出于测试目的,我想将以下自定义 UserDetailsService 添加到 the demo package of the authserver app

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;
    }
}

如您所见,这个UserDetailsService 还不是autowired,它故意使用不安全的密码,因为它只是为测试目的而设计的。

需要对the GitHub sample app 进行哪些特定更改,以便用户可以使用password=TheShire 登录为username=Samwise,或者使用username=Frodo 登录password=MyRing发到AuthserverApplication.java 或其他地方?


建议:


Spring OAuth2 Developer Guide 表示使用GlobalAuthenticationManagerConfigurer 全局配置UserDetailsService。然而,谷歌搜索这些名字产生的结果并不那么有用。

另外,一个使用内部 spring 安全性 INSTEAD OF OAuth 的不同应用程序使用以下语法来连接 UserDetailsService,但我不确定如何将其语法调整为当前 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);
    }
}

我尝试在OAuth2AuthorizationConfig 中使用@AutowireUsers 连接到AuthorizationServerEndpointsConfigurer,如下所示:

@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
        ;
}

但是 Spring Boot 日志显示用户 Samwise 没有找到,这意味着 UserDetailsService 没有成功挂接。以下是 Spring Boot 日志的相关摘录:

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

我还能尝试什么?

【问题讨论】:

    标签: spring spring-security spring-boot spring-security-oauth2 spring-oauth2


    【解决方案1】:

    我在使用 Spring Security 开发我的 oauth 服务器时遇到了类似的问题。我的情况略有不同,因为我想添加一个 UserDetailsService 来验证刷新令牌,但我认为我的解决方案也会对您有所帮助。

    和你一样,我首先尝试使用AuthorizationServerEndpointsConfigurer 指定UserDetailsService,但这不起作用。我不确定这是错误还是设计使然,但需要在AuthenticationManager 中设置UserDetailsService,以便各种oauth2 类可以找到它。这对我有用:

    @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
      }
    
    }
    

    我认为如果您从第 73 行开始更改以下内容,它可能对您有用:

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

    您当然还需要在WebSecurityConfigurerAdapter 的某个位置添加@Autowired Users userDetailsService;

    我想提的其他事情:

    1. 这可能是特定于版本的,我使用的是 spring-security-oauth2 2.0.12
    2. 我无法引用任何资料来说明为什么会这样,我什至不确定我的解决方案是真正的解决方案还是 hack。
    3. 指南中提到的GlobalAuthenticationManagerConfigurer 几乎可以肯定是一个错字,我在Spring 中的任何东西的源代码中都找不到该字符串。

    【讨论】:

    • Manan,我想我在代码中找到了“全局”AuthenticationManagerBuilder。该字符串文字不会发生这种情况,但globalAuthBuilder 会发生这种情况。在下面查看我发布的解决方案。
    【解决方案2】:

    我遇到了同样的问题,最初的解决方案与 Manan Mehta 发布的相同。就在最近,spring security 和 spring oauth2 的某些版本组合导致尝试刷新令牌,从而导致 HTTP 500 错误,指出我的日志中的 UserDetailsService is required

    相关的堆栈跟踪如下所示:

    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)
    

    您可以在底部看到DefaultTokenServices 正在尝试刷新令牌。然后它会调用AuthenticationManager 重新进行身份验证(以防用户撤销权限或用户被删除等),但这就是一切都解开的地方。您在堆栈跟踪的顶部看到UserDetailsServiceDelegator 是调用loadUserByUsername 而不是我漂亮的UserDetailsService。即使在我的WebSecurityConfigurerAdapter 中我设置了UserDetailsService,还有另外两个WebSecurityConfigurerAdapters。一个用于ResourceServerConfiguration,一个用于AuthorizationServerSecurityConfiguration,这些配置永远不会得到我设置的UserDetailsService

    在通过 Spring Security 一路追踪拼凑发生的事情时,我发现有一个“本地”AuthenticationManagerBuilder 和一个“全局”AuthenticationManagerBuilder,我们需要在全局版本中设置它以便将此信息传递给这些其他构建器上下文。

    因此,我想出的解决方案是以与其他上下文获取全局版本相同的方式获取“全局”版本。在我的WebSecurityConfigurerAdapter 里面,我有以下内容:

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

    这奏效了。其他上下文现在有我的UserDetailsService。我把这个留给将来偶然发现这个雷区的勇敢的士兵。

    【讨论】:

      【解决方案3】:

      对于任何人在执行刷新令牌时遇到UserDetailsService is required 错误,并且您确认您已经拥有UserDetailsService bean。

      尝试添加这个:

      @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);
          }
      }
      

      这是我的尝试和错误,可能不适合你。

      顺便说一句,如果你放弃“猜测”哪个bean会在spring之前选择,你可以扩展AuthorizationServerConfigurerAdapterWebSecurityConfigurerAdapter并自己配置所有东西,但我认为它失去了spring autoconfig的力量。 如果我只需要自定义一些配置,为什么我需要配置所有内容?

      【讨论】:

        【解决方案4】:

        我的要求是从 oauth2 电子邮件属性后面获取一个数据库对象。我发现了这个问题,因为我假设我需要创建一个自定义用户详细信息服务。实际上,我需要实现 OidcUser 界面并挂钩到该过程。

        最初我认为它是 OAuth2UserService,但我已经设置了我的 AWS Cognito 身份验证提供程序,以便它是开放的 id 连接..

        //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
        }
        

        自定义服务是任何定制逻辑都可以使用的地方。 我计划在我的CustomUserPrincipal 上实现UserDetails 接口,这样我就可以有不同的身份验证机制进行实时和测试,以促进自动化的 ui 测试。

        【讨论】:

          猜你喜欢
          • 2013-02-11
          • 2017-10-27
          • 2015-10-19
          • 2013-05-12
          • 2012-12-13
          • 2017-04-13
          • 2014-10-12
          • 2014-10-06
          • 2016-03-09
          相关资源
          最近更新 更多