【问题标题】:refresh_token grant_type error: UserDetailsService is required. But I dont want to specify onerefresh_token grant_type 错误:需要 UserDetailsS​​ervice。但我不想指定一个
【发布时间】:2019-11-19 10:05:56
【问题描述】:

我正在尝试使用 Spring Boot 和依赖项创建 Oauth 身份验证/授权服务器 * spring-security-oauth2-autoconfigure * nimbus-jose-jwt

我正在关注docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-oauth2-authorization-server

问题是我不想指定 UserDetailsS​​ervice,因为有关用户帐户的信息位于另一个不公开密码的服务中。该服务只有一个 API,其中输入是用户/密码,输出是用户信息(如果用户存在/凭据正确)。

所以我的代码/配置有点偏离文档。

@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

     //injections

   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
       endpoints
               .tokenStore(jwtTokenStore)
               .accessTokenConverter(accessTokenConverter)
               .authenticationManager(authenticationManager);
   }
}

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

   //injections

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

    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(travelerAuthenticationProvider); //my custom // authentication provider that calls the other service for checking credentials
    }
}

@Component
public class TravelerAuthenticationProvider implements AuthenticationProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(TravelerAuthenticationProvider.class);

    private OrderTravelerProfileClient travelerProfileClient;

    public TravelerAuthenticationProvider(OrderTravelerProfileClient travelerProfileClient) {
        this.travelerProfileClient = travelerProfileClient;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (authentication.getName() == null || (authentication.getCredentials().toString().isEmpty())) {
            return null;
        }
        var username = authentication.getName();
        var password = authentication.getCredentials().toString();
        try {
            travelerProfileClient.authenticate(username, password);
        } catch (Exception e) {
            LOGGER.error("checking traveler {} credentials failed", username, e);
            throw new BadCredentialsException("wrong traveler credentials");
        }
        var authorities = Set.of(new SimpleGrantedAuthority("traveler"));
        var updatedAuthentication = new UsernamePasswordAuthenticationToken(username, password, authorities);
        return updatedAuthentication;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

与 client_credentials 和密码流相关的所有内容都有效,但是当我尝试使用 refresh_token 流时,它抱怨 UserDetailsService is required。我应该如何在不定义 UserDetailsS​​ervice 并仅中继我的自定义身份验证提供程序的情况下解决问题?

更新: 显然 refresh_token 流程对身份验证(凭据)进行了重新检查,这需要另一个身份验证提供程序用于 PreAuthenticatedAuthenticationToken.class 类型。

所以我像这样创建了一个新的身份验证提供程序:

@Component
public class TravelerRefreshTokenBasedAuthenticationProvider implements AuthenticationProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(TravelerRefreshTokenBasedAuthenticationProvider.class);


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        var currentAuthentication = (PreAuthenticatedAuthenticationToken) authentication;
            //.....
        return updatedAuthentication;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(PreAuthenticatedAuthenticationToken.class);
    }
}

并将我的安全配置更新为:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

//injections

    //this bean will be more configured by the method below and it will be used by spring boot
    //for authenticating requests. Its kind of an equivalent to userDetailsService
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(travelerUserPassBasedAuthenticationProvider);
        authenticationManagerBuilder.authenticationProvider(travelerRefreshTokenBasedAuthenticationProvider);
    }
}

问题是 spring 在 refresh_token 流程中无法识别我的身份验证提供程序,并尝试使用默认提供程序。而默认的是尝试使用一个不存在的 UserDetailsS​​ervice。

我也觉得我不需要创建另一个提供程序,我可以重用以前的提供程序。因为 spring 未能使用我的自定义提供程序的检查是对用户/通过的检查;我在以前的身份验证提供程序中所做的。

总而言之,到现在为止,我觉得我必须介绍我的自定义提供程序,以使 refresh_token 流与密码流相比具有不同的弹出方式

【问题讨论】:

    标签: spring spring-boot oauth-2.0 spring-security-oauth2 refresh-token


    【解决方案1】:

    您的AuthenticationProvider 实现仅支持UsernamePasswordAuthenticationToken,它用于用户名/密码身份验证,而refresh_token 流尝试使用PreAuthenticatedAuthenticationToken 更新身份验证(请参阅DefaultTokenServices.java)。

    因此您需要为PreAuthenticatedAuthenticationToken 创建另一个AuthenticationProvider 并将其添加到AuthenticationManagerBuilder

    更新:

    我发现AuthorizationServerEndpointsConfigurer 创建了一个新的DefaultTokenServices 实例,如果没有分配,这又会创建一个PreAuthenticatedAuthenticationProvider 的新实例并且不使用提供的AuthenticationManager。为避免这种情况,您可以创建自己的 DefaultTokenServices 实例并将其传递给 AuthorizationServerEndpointsConfigurer

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .tokenStore(jwtTokenStore)
                .accessTokenConverter(accessTokenConverter)
                .tokenEnhancer(accessTokenConverter)
                .authenticationManager(authenticationManager)
                .tokenServices(createTokenServices(endpoints, authenticationManager));
    }
    
    private DefaultTokenServices createTokenServices(AuthorizationServerEndpointsConfigurer endpoints, AuthenticationManager authenticationManager) {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAuthenticationManager(authenticationManager);
        return tokenServices;
    }
    

    【讨论】:

    • 我试过了。不幸的是,没有区别。我的新 AuthPrivoder 没有被调用。 @anar-sultanov
    • @Override protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) { authenticationManagerBuilder.authenticationProvider(travelerUserPassBasedAuthenticationProvider); authenticationManagerBuilder.authenticationProvider(travelerRefreshTokenBasedAuthenticationProvider); }
    • 我调试了库端代码(从您在示例中链接的代码开始)。尽管我将其介绍给了 AuthenticationManagerBuilder,但我的身份验证提供程序都没有在 refresh_token 流程中被识别
    • ``` @Component public class TravelerRefreshTokenBasedAuthenticationProvider implements AuthenticationProvider { //..authentication method @Override public boolean supports(Class> authentication) { return authentication.equals(PreAuthenticatedAuthenticationToken.class); } } ```
    • @NeoSmith 我猜你的accessTokenConverterJwtAccessTokenConverter 的实例,它实现了TokenEnhancer。因此,您可以将其传递给端点,它将被拾取。更正了答案,如果对你有用,你能接受吗?
    猜你喜欢
    • 2017-09-17
    • 2018-02-13
    • 1970-01-01
    • 2020-06-01
    • 2020-05-21
    • 2015-08-07
    • 2013-05-12
    • 2012-06-11
    • 2017-10-06
    相关资源
    最近更新 更多