【问题标题】:Spring OAuth 2 + JWT Inlcuding additional info JUST in access tokenSpring OAuth2 + JWT 在访问令牌中包括附加信息
【发布时间】:2018-03-06 12:33:43
【问题描述】:

我可以在实现我自己的 TokenEnhancer 的访问令牌中包含其他信息,但此类信息包含两次。一个在编码的 access_token 中,另一个在身份验证服务器响应中。

长话短说!我请求具有正确凭据的访问令牌,并得到以下响应:

{
    "access_token" : "eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRJZCI6Ik1ZX0NVU1RPTV9JTkZPX0NMSUVOVCIsInVzZXJfbmFtZSI6IlVTRVIiLCJzY29wZSI6WyJGT08iXSwiZXhwIjoxNTA2MzkwOTM5LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiZjJkYWFkM2ItYzkzOC00ZjExLWI3ODctMzExZDdlNjYzYzhhIiwiY2xpZW50X2lkIjoid2ViX2FwcCJ9.IdgYRxwZGRPR97nxHpAcJXNWDTShQE1tsg9NsBwlOk8eDWE1B-mjfGTaKiyTO1-m9GBpXnxt2PaOV7AbdLsCZ5xLPUR0_5ehuNB6WCXLSkdac5xbw-rmNdJHTe9gLJizOZAKF6J-_Xo9OOQISKBqliY5vo5y0btqIw4CX6-ukYoWZmwHThwnAsEA_PqGuEXsbXMGz-vqJaSVpvJeEOBNL0KOh-cNxc0ft-rJ3snjPerN_efAiZdFkzxdCeuoGmZvSyHRjYR8kQ3ZqZ5MOunw9YuTvidL1IK5TODHQ2BjiCTpbgDlYx-Oh5UxcYNrPOhD-tBjRuuqDSz8K6ddpke4RQ",
    "token_type" : "bearer",
    "refresh_token" : "eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRJZCI6Ik1ZX0NVU1RPTV9JTkZPX0NMSUVOVCIsInVzZXJfbmFtZSI6IlVTRVIiLCJzY29wZSI6WyJGT08iXSwiYXRpIjoiZjJkYWFkM2ItYzkzOC00ZjExLWI3ODctMzExZDdlNjYzYzhhIiwiZXhwIjoxNTA4OTM5NzM5LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiOGU2Zjc0OTEtMmQ3MC00NTUwLThhMDgtZjk0YjkzYTVkYWZmIiwiY2xpZW50X2lkIjoid2ViX2FwcCJ9.MqwMrYrofu7pUQu2mF33__h6M4OWSRrQ-lc8JzTn0DkpJ6a3-yjnjjppZ9fs3KBz_lpRIO8jo--eId449rEjP4M3_9lDRSW9_HyBAvd57OtyUHa5SPM9prD6ReXGCyiIw2gO07euIf-Vp4UHsjoKK0MdtfMmFIWms1JMGFBmzBha8kqKaMxKzppGy-jVdP7384K9oovD20H-NubjScfoO2Crp1cTM-SXc-0v6kwB1qV-cI6HKXmbkoFhbH2bL_nRvXTkLYI-UvRNTNLHzqhcqztLTrszcWa2BjNU2IofsNByFS8BHTDV1vu0BqZA4kfNCJcFJ89tBDt2L8vfFkYezQ",
    "expires_in" : 43199,
    "scope" : "FOO",
    "clientId" : "MY_CUSTOM_INFO_CLIENT",
    "jti" : "f2daad3b-c938-4f11-b787-311d7e663c8a"
}

所以我可以看到响应中包含的 clientId... 现在我复制我的 access_token 并解码为:https://jwt.io/

并且在有效载荷中还包括 clientId...

我的问题是:如何从服务器响应中删除附加信息并将其保留在令牌(access_token 和 refresh_token)中。

请看下面的代码:

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

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

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(
                  Arrays.asList(tokenEnhancer(), accessTokenConverter()));
        endpoints
                .tokenStore(tokenStore())
                .authenticationManager(authenticationManager)
                .tokenEnhancer(tokenEnhancerChain);
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

       clients.inMemory()
           .withClient("web_app")
           .secret("web_app123")
           .scopes("FOO")
           .autoApprove(true)
           .authorities("FOO_READ", "FOO_WRITE")
           .authorizedGrantTypes("refresh_token", "password");
 }
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory = 
          new KeyStoreKeyFactory(new ClassPathResource("mykey.jks"), "mykey123".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mykey"));
        return converter;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

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

还有我的 CustomTokenEnhancer:

import java.util.HashMap;
import java.util.Map;

import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

import com.mapflow.ms.security.service.UserDetailInfo;

public class CustomTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
            OAuth2Authentication authentication) {
        UserDetailInfo user = (UserDetailInfo) authentication.getPrincipal();
        final Map<String, Object> additionalInfo = new HashMap<String, Object>();

        additionalInfo.put("clientId", user.getClientId());

        ((DefaultOAuth2AccessToken) accessToken)
                .setAdditionalInformation(additionalInfo);

        return accessToken;
    }
}

【问题讨论】:

    标签: spring jwt spring-security-oauth2


    【解决方案1】:

    过了一会儿,我想通了。 JwtAccessTokenConverter 也实现了 TokenEnhaner。首先调用CustomTokenEnhaner.enhance,包括附加信息。然后JwtAccessTokenConverter.enhance,将AccessToken 编码为CustomTokenEnhaner.enhance,并在响应中包含附加信息。这个想法是初始化DefaultOAuth2AccessToken.additionalInformation 一次编码在access_token 中。解决办法是:

    首先让CustomTokenEnhancer扩展JwtAccessTokenConverter,覆盖enhance,附加附加信息,从父级调用enhance并初始化DefaultOAuth2AccessToken.additionalInformation

    public class CustomTokenConverter extends JwtAccessTokenConverter {
    
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
                OAuth2Authentication authentication) {
            if(authentication.getOAuth2Request().getGrantType().equalsIgnoreCase("password")) {
                UserDetailInfo user = (UserDetailInfo) authentication.getPrincipal();
                final Map<String, Object> additionalInfo = new HashMap<String, Object>();
    
                additionalInfo.put("clientId", user.getClientId());
    
                ((DefaultOAuth2AccessToken) accessToken)
                        .setAdditionalInformation(additionalInfo);    
            } 
            accessToken = super.enhance(accessToken, authentication);
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(new HashMap<>());
            return accessToken;
        }
    }
    

    最后一步,就是删除 bean

    @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            KeyStoreKeyFactory keyStoreKeyFactory = 
              new KeyStoreKeyFactory(new ClassPathResource("mykey.jks"), "mykey123".toCharArray());
            converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mykey"));
            return converter;
        }
    

    并将密钥添加到 CustomTokenEnhancer

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        CustomTokenConverter tokenConverter = new CustomTokenConverter();
        tokenConverter.setSigningKey("PswMapview2017");
        return tokenConverter;
    }
    

    就是这样。

    【讨论】:

    • ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(new HashMap&lt;&gt;()); 似乎是多余的
    • 这实际上是重要的一行。我希望将额外信息放入编码的访问令牌中,而不是放入响应中。
    猜你喜欢
    • 2017-11-21
    • 2019-12-30
    • 2017-02-06
    • 2017-08-31
    • 2016-05-10
    • 2018-08-03
    • 1970-01-01
    • 2021-08-31
    • 2021-05-22
    相关资源
    最近更新 更多