spring-security-oauth2 2.0.7 刷新令牌 UserDetailsS​​ervice 配置 - UserDetailsS​​ervice 是必需的

Posted

技术标签:

【中文标题】spring-security-oauth2 2.0.7 刷新令牌 UserDetailsS​​ervice 配置 - UserDetailsS​​ervice 是必需的【英文标题】:spring-security-oauth2 2.0.7 refresh token UserDetailsService Configuration - UserDetailsService is required 【发布时间】:2015-08-07 21:17:36 【问题描述】:

我有一个关于 spring-security-oauth2 2.0.7 配置的问题。 我正在通过 GlobalAuthenticationConfigurerAdapter 使用 LDAP 进行身份验证:

@SpringBootApplication
@Controller
@SessionAttributes("authorizationRequest")
public class AuthorizationServer extends WebMvcConfigurerAdapter 

    public static void main(String[] args) 
        SpringApplication.run(AuthorizationServer.class, args);
    

    @Override
    public void addViewControllers(ViewControllerRegistry registry) 
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/oauth/confirm_access").setViewName("authorize");
    

    @Configuration
    public static class JwtConfiguration 

        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() 
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            KeyPair keyPair = new KeyStoreKeyFactory(
                    new ClassPathResource("keystore.jks"), "foobar".toCharArray())
                    .getKeyPair("test");
            converter.setKeyPair(keyPair);
            return converter;
        

        @Bean
        public JwtTokenStore jwtTokenStore()
            return new JwtTokenStore(jwtAccessTokenConverter());
        
    


    @Configuration
    @EnableAuthorizationServer
    public static class OAuth2Config extends AuthorizationServerConfigurerAdapter implements EnvironmentAware 

        private static final String ENV_OAUTH = "authentication.oauth.";
        private static final String PROP_CLIENTID = "clientid";
        private static final String PROP_SECRET = "secret";
        private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";

        private RelaxedPropertyResolver propertyResolver;

        @Inject
        private AuthenticationManager authenticationManager;

        @Inject
        private JwtAccessTokenConverter jwtAccessTokenConverter;

        @Inject
        private JwtTokenStore jwtTokenStore;

        @Inject
        private UserDetailsService userDetailsService;

        @Override
        public void setEnvironment(Environment environment) 
            this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
        

        @Bean
        @Primary
        public DefaultTokenServices tokenServices() 
            DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setTokenStore(jwtTokenStore);
            tokenServices.setAuthenticationManager(authenticationManager);
            return tokenServices;
        


        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception 
            endpoints.authenticationManager(authenticationManager).tokenStore(jwtTokenStore).accessTokenConverter(
                    jwtAccessTokenConverter).userDetailsService(userDetailsService);
        

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer)
                throws Exception 
            oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
                    "isAuthenticated()");
        

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
            clients.inMemory()
                    .withClient(propertyResolver.getProperty(PROP_CLIENTID))
                    .scopes("read", "write")
                    .authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
                    .authorizedGrantTypes("authorization_code", "refresh_token", "password")
                    .secret(propertyResolver.getProperty(PROP_SECRET))
                    .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
        
    

    @Configuration
    @Order(-10)
    protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter 

        @Override
        protected void configure(HttpSecurity http) throws Exception 
            http
                    .formLogin().loginPage("/login").permitAll()
                    .and()
                    .requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
                    .and()
                    .authorizeRequests().anyRequest().authenticated();
        

        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception 
            return super.authenticationManagerBean();
        

        @Bean
        @Override
        public UserDetailsService userDetailsServiceBean() throws Exception 
            return super.userDetailsServiceBean();
        
    

    @Configuration
    protected static class AuthenticationConfiguration extends
            GlobalAuthenticationConfigurerAdapter 

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception 
            auth
                    .ldapAuthentication()
                    .userDnPatterns("uid=0,ou=people")
                    .groupSearchBase("ou=groups")
                    .contextSource().ldif("classpath:test-server.ldif");
        
    

虽然刷新令牌适用于 spring-security-oauth2 的 2.0.6 版,但它不再适用于 2.0.7 版。 如阅读here,应设置AuthenticationManager,以便在刷新期间尝试获取新的访问令牌时使用。

据我了解,这与 spring-security-oauth2 的 following 更改有关。

很遗憾,我没有设法正确设置它。

org.springframework.security.oauth2.provider.token.DefaultTokenServices#setAuthenticationManager

被调用并获得AuthenticationManager 注入。我不确定我是否理解LdapUserDetailsService 将如何被注入。我唯一看到的是在令牌刷新调用期间尝试重新验证用户身份时将调用PreAuthenticatedAuthenticationProvider

谁能告诉我怎么做?

ps:我得到的异常如下:

p.PreAuthenticatedAuthenticationProvider : PreAuthenticated authentication request: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@5775: Principal: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d5545: Principal: bob; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
o.s.s.o.provider.endpoint.TokenEndpoint  : Handling error: IllegalStateException, UserDetailsService is required.

【问题讨论】:

如here、userDetailsService: if you inject a UserDetailsService or if one is configured globally anyway (e.g. in a GlobalAuthenticationManagerConfigurer) then a refresh token grant will contain a check on the user details, to ensure that the account is still active. 所述,我希望LdapUserDetailsService 会被自动使用。 我也会(它对我有用)。你能发布一个完整的项目吗? @dave-syer,非常感谢您的回复。我在以下测试项目中隔离了问题:github.com/jhoelter/zaas/tree/master/authserver 有一个名为 spring-security-oauth2-2.0.6 的标签,其中刷新令牌工作正常。当切换到 2.0.7 并设置上述配置时,它会崩溃。非常感谢您的帮助 好的,我看到了问题。 LdapAuthenticationProvider 不受 UserDetailsService 的支持,因此您实际上在任何地方都没有(过滤器链中的空委托除外)。我会尝试一下,看看是否有解决方法,或者我们可以添加的新功能。 我也遇到了同样的问题,Spring 框架简直就是一场噩梦!因为我使用的是 AuthenticationProvider 实现而不是 UserDetailService,但是如果我像 Jereremie 所说的那样将库降级到 2.0.6,它可以工作,但是现在使用grant_type=user_credentials 登录不起作用,Spring 不会映射权限而且我不知道为什么,因为角色在那里,当您解码令牌时可以使用任何 JWT 工具查看。 【参考方案1】:

当我使用自定义 AuthenticationProvider 而不是 UserDetailsService 实现来解决登录身份验证的 JWT 令牌实现 OAuth2 服务器时,我遇到了类似的问题。

但最近我发现如果您希望refresh_token 正常工作,Spring 引发的错误是正确的。对于AuthenticationProvider 实现,不可能用refresh_token 刷新令牌,因为在这种实现中,您必须解决密码是否正确,但刷新令牌没有该信息。但是,UserDetailsService 与密码无关。

spring-security-oauth2 的 2.0.6 版有效,因为从不检查用户授权,只检查刷新令牌是否有效(使用私钥签名),但是,如果用户在首次登录后从系统中删除,使用刷新令牌,被删除的用户将有无限时间访问您的系统,这是一个很大的安全问题。

看看我报告的问题:https://github.com/spring-projects/spring-security-oauth/issues/813

【讨论】:

【参考方案2】:

按照 Dave Syer 的建议,我创建了一个自定义 LdapUserDetailsService。 可以在以下tag 下找到工作解决方案。

应用上下文

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <context:property-placeholder location="application.yml"/>

    <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
        <constructor-arg value="$authentication.ldap.url" />
    </bean>

    <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
        <constructor-arg index="0" value="$authentication.ldap.userSearchBase" />
        <constructor-arg index="1" value="uid=0" />
        <constructor-arg index="2" ref="contextSource"/>
    </bean>

    <bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
        <constructor-arg index="0" ref="contextSource"/>
        <constructor-arg index="1" value="$authentication.ldap.groupSearchBase"/>
        <property name="groupSearchFilter" value="$authentication.ldap.groupSearchFilter"/>
    </bean>

    <bean id="myUserDetailsService"
          class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
        <constructor-arg index="0" ref="userSearch"/>
        <constructor-arg index="1" ref="ldapAuthoritiesPopulator"/>
    </bean>

</beans>

属性

authentication:
 ldap:
  url: ldap://127.0.0.1:33389/dc=springframework,dc=org
  userSearchBase:
  userDnPatterns: uid=0,ou=people
  groupSearchBase: ou=groups
  groupSearchFilter: (uniqueMember=0)

【讨论】:

【参考方案3】:

OAuth 部分需要创建一个LdapUserDetailsService,其查询与您的身份验证器相同,并将其注入AuthorizationServerEndpointsConfigurer。我认为不支持以@Configuration 样式创建UserDetailService(可能值得在JIRA 中为此开张票),但看起来您可以在XML 中完成。

【讨论】:

嗨,戴夫,非常感谢您的反馈。我设置了一个自定义LdapUserDetailsService(使用旧式 XML ApplicationContext)。可以在tag 下找到实现。 我会为此创建一张票。我发现必须创建第二个LdapUserDetailsService 相当繁重,即使在调用org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder#ldapAuthentication 时自动配置了一个@ 这篇文章对我帮助很大。我使用的是旧版本的 Spring Security (1.x),但遇到了同样的问题。我意识到我们有一个 @Bean 敌人 UserDetailsS​​ervice ,其他一切似乎都在工作。但我需要向配置器添加配置以了解 UserDetailsS​​ervice。感谢您的评论,多年后提供帮助。

以上是关于spring-security-oauth2 2.0.7 刷新令牌 UserDetailsS​​ervice 配置 - UserDetailsS​​ervice 是必需的的主要内容,如果未能解决你的问题,请参考以下文章

spring-security-oauth2中的HttpSecurity配置问题

如何处理 spring-security-oauth2 的版本升级?

spring-security-oauth2 2.0.7 刷新令牌 UserDetailsS​​ervice 配置 - UserDetailsS​​ervice 是必需的

Spring-Security-Oauth2:默认登录成功 url

spring-security-oauth2 替代品

spring-security-oauth2注解详解