Spring OAuth2中Token Endpoint的自定义

Posted

技术标签:

【中文标题】Spring OAuth2中Token Endpoint的自定义【英文标题】:Customization of TokenEndpoint in Sprin OAuth2 【发布时间】:2019-08-07 21:24:30 【问题描述】:

我想在 Spring 框架中提供 TokenEndpoint 类的自定义实现。

我已经复制了 spring 的 TokenEndpoint 类,并对需要的地方进行了更改。但是当应用程序启动时,我总是收到错误

Caused by: java.lang.IllegalStateException: TokenGranter must be provided

我在我的 OAuthConfig 中为 TokenGranter 提供了一个实现,但是 spring 没有接受它

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception 
    endpoints.pathMapping("/oauth/token", "/oauth/token/v1")
             .tokenServices(tokenServices())
             .tokenGranter(tokenGranter())
            .authenticationManager(authenticationManager).tokenStore(tokenStore())
            .tokenEnhancer(tokenEnhancer()).accessTokenConverter(accessTokenConverter());


@Bean
@Primary
public TokenGranter tokenGranter() 
    TokenGranter tokenGranter = null;
    if (tokenGranter == null) 
        tokenGranter = new TokenGranter() 
            private CompositeTokenGranter delegate;

            @Override
            public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) 
                if (delegate == null) 
                    delegate = new CompositeTokenGranter(getDefaultTokenGranters());
                
                return delegate.grant(grantType, tokenRequest);
            
        ;
    
    return tokenGranter;

我什至尝试在我的自定义 TokenEndpoint 类中提供这个实现。 目前,自定义 TokenEndpoint 的实现与 Spring 的 TokenEndpoint 完全相同。

OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

private List<TokenGranter> getDefaultTokenGranters() 
    ClientDetailsService clientDetails = clientDetailsService();
    AuthorizationServerTokenServices tokenServices = tokenServices();
    AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
    OAuth2RequestFactory requestFactory = requestFactory();

    List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
    tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
            requestFactory));
    tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
    ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
    tokenGranters.add(implicit);
    tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
    if (authenticationManager != null) 
        tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails,
                requestFactory));
    
    return tokenGranters;


private DefaultTokenServices createDefaultTokenServices() 
    DefaultTokenServices tokenServices = new DefaultTokenServices();
    tokenServices.setTokenStore(tokenStore());
    tokenServices.setSupportRefreshToken(true);
    tokenServices.setReuseRefreshToken(true);
    tokenServices.setClientDetailsService(clientDetailsService());
    tokenServices.setTokenEnhancer(tokenEnhancer());
    addUserDetailsService(tokenServices, new CustomDetailsService());
    return tokenServices;


private ClientDetailsService clientDetailsService() 
    ClientDetailsService clientDetailsService = null;
    clientDetailsService = new InMemoryClientDetailsService();
    addUserDetailsService(createDefaultTokenServices(), new CustomDetailsService());
    return clientDetailsService;


private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) 
    if (userDetailsService != null) 
        PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
        provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
                userDetailsService));
        tokenServices
                .setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider> asList(provider)));
    


private AuthorizationCodeServices authorizationCodeServices() 
    AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();
    return authorizationCodeServices;


private OAuth2RequestFactory requestFactory() 
    OAuth2RequestFactory requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService());
    return requestFactory;


@Bean
public JwtTokenStore tokenStore() 
    JwtTokenStore jwtTokenStore = new JwtTokenStore(accessTokenConverter());
    return jwtTokenStore;


@Bean
@Primary
public AuthorizationServerTokenServices tokenServices() 
    final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setAccessTokenValiditySeconds(-1);
    defaultTokenServices.setTokenStore(tokenStore());
    return defaultTokenServices;


@Bean
public TokenEnhancer tokenEnhancer() 
    return new CustomTokenEnhancer();


@Bean
public JwtAccessTokenConverter accessTokenConverter() 
    final JwtAccessTokenConverter converter = new JwtAccessTokenConverter() 
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) 
            return accessToken;
        
    ;
    return converter;

这几天我一直在尝试解决这个问题,但没有任何运气。因此,我们将不胜感激。

【问题讨论】:

我已经复制了 spring 的 TokenEndpoint 类,并对需要的地方进行了更改。 这是否意味着您有两个 TokenEndpoints 同时运行?将堆栈跟踪添加到您的问题中,然后我们将查看哪个实现引发了异常。 【参考方案1】:

我知道这个问题已经很老了,但是我遇到了同样的问题并且没有找到关于自定义 TokenEndpoint 的完整指南。我无法使用 TokenEnhancer,因为我需要更改响应的标头。所以,这是适合我的版本。

您像往常一样定义覆盖的控制器:

@RequestMapping(value = "/oauth/token")
public class CustomTokenEndpoint extends TokenEndpoint 
  @PostMapping
  public ResponseEntity<OAuth2AccessToken> postAccessToken(
      Principal principal,
      @RequestParam Map<String, String> parameters
  ) throws HttpRequestMethodNotSupportedException 

    ResponseEntity<OAuth2AccessToken> defaultResponse = super.postAccessToken(principal, parameters);

    // do some work

    return defaultResponse;
  

并且您需要创建自己的 TokenEndpoint bean:

  @Bean
  @Primary
  public TokenEndpoint tokenEndpoint(AuthorizationServerEndpointsConfiguration conf) 
    TokenEndpoint tokenEndpoint = new CustomTokenEndpoint();
    tokenEndpoint.setClientDetailsService(conf.getEndpointsConfigurer().getClientDetailsService());
    tokenEndpoint.setProviderExceptionHandler(conf.getEndpointsConfigurer().getExceptionTranslator());
    tokenEndpoint.setTokenGranter(conf.getEndpointsConfigurer().getTokenGranter());
    tokenEndpoint.setOAuth2RequestFactory(conf.getEndpointsConfigurer().getOAuth2RequestFactory());
    tokenEndpoint.setOAuth2RequestValidator(conf.getEndpointsConfigurer().getOAuth2RequestValidator());
    tokenEndpoint.setAllowedRequestMethods(conf.getEndpointsConfigurer().getAllowedTokenEndpointRequestMethods());
    return tokenEndpoint;
  

这是踢球者。您需要允许在 application.properties 中覆盖 spring bean:

spring.main.allow-bean-definition-overriding: true

希望这对某人有所帮助

【讨论】:

【参考方案2】:

为什么需要再次实现TokenEndpoint

您可以创建一个TokenGranter bean 并将其注入到默认端点。

getDefaultTokenGranters() 方法在哪里?

您的 AuthorizationServerEndpointsConfigurer 源代码似乎不完整。

更新

如果您想自定义令牌响应,请使用TokenEnhancer

例如:

public class CustomTokenEnhancer implements TokenEnhancer 

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) 
        OurUser user = (OurUser) authentication.getPrincipal();
        final Map<String, Object> additionalInfo = new HashMap<>();
        Map<String, Object> userDetails = new HashMap<>();
        userDetails.put(USERID, user.getId().getId());
        userDetails.put(NAME, user.getName());
        userDetails.put(MOBILE, user.getMobile());
        userDetails.put(EMAIL, user.getEmail());
        additionalInfo.put(USERINFO, userDetails);
        // Set additional information in token for retriving in #org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    


在 OAuth2 配置中:

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception 
        super.configure(endpoints);
        endpoints.
                .....
                // Include additional information to OAuth2 Access token with custom token enhancer
                .tokenEnhancer(tokenEnhancer());
    

    @Bean
    public TokenEnhancer tokenEnhancer() 
        return new CustomTokenEnhancer();
    

https://***.com/a/28512607/4377110

【讨论】:

我的目标是自定义返回带有访问令牌的响应的方式。因此,我正在实施 TokenEndpoint。我已经为 AuthorizationServerEndpointsConfiguration 中的所有必需方法提供了实现。没有贴到这里,因为方法太多了。我的最终目标是调整从 Endpoint 返回响应的方式,而不是提供新的 TokenGranter。 你应该使用 TokenEnhancer。

以上是关于Spring OAuth2中Token Endpoint的自定义的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud云架构 - SSO单点登录之OAuth2.0 根据token获取用户信息

(十三) 整合spring cloud云架构 - SSO单点登录之OAuth2.0 根据token获

Spring Security OAuth2 将 access_token 存储在 cookie 中

整合spring cloud云架构-SSO单点登录之OAuth2.0根据token获取用户信息

整合spring cloud云架构 - SSO单点登录之OAuth2.0 根据token获取用户信息

Java Spring Security:401 Unauthorized for token OAuth2 端点