将自定义的UserDetailsService添加到Spring Security OAuth2应用程序

16
我该如何将自定义的 UserDetailsService 添加到 这个 Spring OAuth2 示例中?默认的 userpassword 已经在 authserver 应用程序的 application.properties 文件中定义。但是,出于测试目的,我想将以下自定义的 UserDetailsService 添加到 authserver 应用程序的 demo 包中
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,并且它故意使用不安全的密码,因为它仅用于测试目的。

需要对GitHub示例应用程序进行特定更改,以使用户可以使用username=Samwisepassword=TheShireusername=Frodopassword=MyRing登录吗?更改需要在AuthserverApplication.java或其他地方进行吗?


建议:


Spring OAuth2开发人员指南建议使用GlobalAuthenticationManagerConfigurer在全局配置UserDetailsService。但是,对这些名称进行谷歌搜索会得到不太有帮助的结果。

此外,另一个使用内部Spring Security而不是OAuth的应用程序使用以下语法连接UserDetailsService,但我不确定如何调整其语法以适应当前操作:

@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中使用@Autowire,以便将Users连接到AuthorizationServerEndpointsConfigurer,如下所示:

我尝试在OAuth2AuthorizationConfig中使用@Autowire,以便将Users连接到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

还可以尝试什么?

4个回答

7

我的要求是根据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测试。


7
我在使用Spring Security开发我的oauth服务器时遇到了类似的问题。我的情况略有不同,我想添加一个UserDetailsService来认证刷新令牌,但我认为我的解决方案也会对您有所帮助。
和您一样,我首先尝试使用AuthorizationServerEndpointsConfigurer指定UserDetailsService,但这种方法不起作用。我不确定这是一个bug还是故意设计的,但是UserDetailsService需要在AuthenticationManager中设置,以便各种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中会发生。请查看我下面发布的解决方案。 - Michael Ressler

6

我遇到了同样的问题,并且最初的解决方案与Manan Mehta发布的相同。最近,Spring Security和Spring OAuth2的某些版本组合导致任何尝试刷新令牌都会导致HTTP 500错误,其中日志显示需要UserDetailsService

相关的堆栈跟踪如下:

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 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。我将此留在这里,供将来遇到这个雷区的勇敢士兵参考。


这真是救命稻草。还要感谢你提供了背后的背景。我将globalAuthBuilder.userDetailsService(..)与我的userDetailsManager bean进行了连接。DefaultTokenServices能够刷新访问令牌。 - Ashutosh Mohanty

1
对于在刷新令牌时遇到“需要UserDetailsService”错误的任何人,并且您确认已经有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);
    }
}

这是我的尝试和错误,也许对你不起作用。 顺便说一下,如果你放弃“猜测”Spring会选择哪个bean,你可以扩展AuthorizationServerConfigurerAdapterWebSecurityConfigurerAdapter并自己配置所有内容,但我认为这失去了Spring自动配置的优势。 如果我只需要自定义一些配置,为什么要配置所有东西?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接