将自定义 UserDetailsS​​ervice 添加到 Spring Security OAuth2 应用程序

Add custom UserDetailsService to Spring Security OAuth2 app

如何将下面的自定义 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 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 或其他地方进行哪些具体更改?


建议:


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

此外,另一个使用内部 spring 安全性而不是 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 引导日志显示未找到用户 Samwise,这意味着 UserDetailsService 未成功连接。以下是 Spring 引导日志的相关摘录:

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 安全性的 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. 我无法引用任何来源来说明为什么会这样,我什至不确定我的解决方案是真正的解决方案还是黑客。
  3. 指南中提到的 GlobalAuthenticationManagerConfigurer 几乎可以肯定是错字,我在 Spring 中的任何内容的源代码中都找不到该字符串。

我 运行 遇到了同样的问题,最初的解决方案与 Manan Mehta 发布的相同。就在最近,spring 安全和 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,但还有另外两个 WebSecurityConfigurerAdapter。一种用于 ResourceServerConfiguration,一种用于 AuthorizationServerSecurityConfiguration,这些配置永远不会得到我设置的 UserDetailsService

一路追查Spring安全拼凑到底是怎么回事,发现有一个"local"AuthenticationManagerBuilder和一个"global"AuthenticationManagerBuilder 我们需要在全局版本上设置它,以便将此信息传递给这些其他构建器上下文。

所以,我想出的解决方案是获取 "global" 版本,就像其他上下文获取全局版本一样。在我的 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。我把这个留给未来偶然发现这个雷区的任何勇敢的士兵。

任何人在执行刷新令牌时遇到 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);
    }
}

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

顺便说一下,如果你放弃 "guess" 哪个 bean 将由 spring 挑选,你可以扩展 AuthorizationServerConfigurerAdapterWebSecurityConfigurerAdapter 并自己配置所有东西,但我认为它失去了 spring 自动配置的能力。 如果我只需要自定义一些配置,为什么我需要配置一切?

我的要求ui评论是从 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 测试。