刷新令牌时找不到身份验证提供程序 - Spring OAuth2 Java配置

7

我有一个Spring Boot项目,其中配置了一个部分工作的Spring OAuth2身份验证过程。我可以成功进行身份验证,但当我尝试获取刷新令牌时,会出现异常。

OAuth配置:

@Configuration
public class OAuth2ServerConfiguration {

    private static final String RESOURCE_ID = "xxx";

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(RESOURCE_ID);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
            .authorizeRequests()
            .antMatchers("/api/**").authenticated();
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Value("${clientDetailsService.clientName}")
        private String clientName;

        @Value("${clientDetailsService.clientSecret}")
        private String clientSecret;

        @Autowired
        @Qualifier("authenticationManager")
        private AuthenticationManager authenticationManager;

        @Autowired
        private ClientDetailsService clientDetailsService;

        @Autowired
        @Qualifier("tokenServices")
        private AuthorizationServerTokenServices tokenServices;

        @Autowired
        @Qualifier("codeServices")
        private AuthorizationCodeServices codeServices;

        @Autowired
        @Qualifier("requestFactory")
        private OAuth2RequestFactory requestFactory;

        @Autowired
        @Qualifier("tokenGranter")
        private TokenGranter tokenGranter;

        private final TokenStore tokenStore = new InMemoryTokenStore();

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.setClientDetailsService(clientDetailsService);
            endpoints.tokenServices(tokenServices)
                        .tokenStore(tokenStore)
                        .authorizationCodeServices(codeServices)
                        .authenticationManager(authenticationManager)
                        .requestFactory(requestFactory)
                        .tokenGranter(tokenGranter);
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.withClientDetails(clientDetailsService);
        }


        @Bean(name = "tokenGranter")
        @Primary
        public TokenGranter tokenGranter() {
            final List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();

            tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, codeServices, clientDetailsService, requestFactory));
            tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
            tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));
            tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
            tokenGranters.add(new CustomTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));

            return new CompositeTokenGranter(tokenGranters);
        }

        @Bean
        @Primary
        public ClientDetailsService clientDetailsService(){
            final InMemoryClientDetailsServiceBuilder builder = new InMemoryClientDetailsServiceBuilder();
            builder.withClient(clientName)
                    .authorizedGrantTypes("password", "refresh_token")
                    .authorities("USER")
                    .scopes("read", "write")
                    .resourceIds(RESOURCE_ID)
                    .secret(clientSecret);

            try {
                return builder.build();
            } catch (final Exception e) {
                e.printStackTrace();
            }

            return null;

        }

        @Bean(name = "tokenServices")
        @Primary
        public AuthorizationServerTokenServices tokenServices() {
            final DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setClientDetailsService(clientDetailsService);
            tokenServices.setTokenStore(tokenStore);
            tokenServices.setAuthenticationManager(authenticationManager);
            return tokenServices;
        }

        @Bean(name = "requestFactory")
        @Primary
        public OAuth2RequestFactory requestFactory() {
            return new DefaultOAuth2RequestFactory(clientDetailsService);
        }

        @Bean(name = "codeServices")
        @Primary
        public AuthorizationCodeServices authorizationCodeServices() {
            return new InMemoryAuthorizationCodeServices();
        }
    }

我还定义了一些自定义组件,例如自定义令牌授权者、自定义身份验证提供程序等。如果需要的话,我会发布它们。
正如我所说,身份验证流程正常工作。当我POST到/oauth/token时,我会得到一个令牌和一个刷新令牌,但是当我尝试使用我的刷新令牌交换新令牌(通过POST http://localhost:8080/oauth/token,grant_type=refresh_token和refresh_token=my refresh token)时,我会收到一个异常: No AuthenticationProvider found for org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken 我在哪里设置身份验证提供程序?如何让Spring也使用我的自定义身份验证提供程序来刷新令牌?
3个回答

10

我通过明确定义PreAuthenticationProvider来解决了这个问题:

@Component("preAuthProvider")
public class CustomPreAuthProvider extends PreAuthenticatedAuthenticationProvider {

    @Autowired
    private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userService;

    public CustomPreAuthProvider(){
        super();
    }

    @PostConstruct
    public void init(){
        super.setPreAuthenticatedUserDetailsService(userService);
    }
}

然后是自定义用户服务:

@Service
public class CustomPreAuthUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {

    @Override
    public final UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) {
    ...    
    }
}

并将此提供程序添加到oauth2配置中:

@Autowired
private AuthenticationProvider authenticationProvider;

@Autowired
@Qualifier("preAuthProvider")
private AuthenticationProvider preAuthProvider;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProvider).authenticationProvider(preAuthProvider);
}

这对我很有帮助,但是您能指导一下我在loadUserDetails上该做什么吗?谢谢。 - dragonalvaro
好的,“loadUserDetails”是您加载请求用户的地方。如何实现这一点(从数据库、从内存中、通过 Web 服务调用)取决于您的实现。@AciD - Jesper N

7

作为 Jesper 答案的另一种替代方式,如果您想要重用当前的 UserDetailsService 来实现此目的,您可以像 Spring 使用他们的 DefaultTokenServices 一样来做:

@Bean
public CustomTokenServices tokenServices() {
    CustomTokenServices tokenServices = new CustomTokenServices();
    tokenServices.setTokenStore(tokenStore());
    tokenServices.setSupportRefreshToken(true);
    tokenServices.setReuseRefreshToken(false);
    tokenServices.setClientDetailsService(clientDetailsService);
    tokenServices.setAuthenticationManager(createPreAuthProvider());
    return tokenServices;
}

private ProviderManager createPreAuthProvider() {
    PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
    provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsService));
    return new ProviderManager(Arrays.asList(provider));
}

UserDetailsService被自动装配到这个@Configuration类中。


2
我不能不同意Jesper的回答,但在我的情况下,通过删除以下内容来修复了相同的错误:

。翻译后为:最初的回答中Jesper的回答是正确的,但是在我的情况下,我通过删除

来解决了相同的错误。
tokenServices.setAuthenticationManager(authenticationManager) 

最初的回答:从tokenService()函数中获取。

1
不设置 authenticationManager 可以“修复”此错误,但我不建议这样做。我已经调试了 Spring 的 DefaultTokenServices,如果 authenticationManager 为空,则刷新令牌不会执行重新认证操作,这意味着它永远不会进入 UserDetailsService 来检查帐户是否仍然是活动状态、未被阻止等。 - Sikor

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