【Spring Security】 Oauth2 授权服务 令牌管理

Metadata

title: 【Spring Security】 Oauth2 授权服务 令牌管理
date: 2023-02-02 22:30
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/SpringSecurity
categories:
  - SpringSecurity
keywords:
  - SpringSecurity
description: 【Spring Security】 Oauth2 授权服务 令牌管理

【Spring Security】 Oauth2 授权服务 令牌管理

OAuth2AccessToken

OAuth2AccessToken 接口定义了 OAuth2 令牌的相关结构和属性,他有一个默认的实现类 DefaultOAuth2AccessToken。

public interface OAuth2AccessToken {
    // 携带令牌访问的前缀
    public static String BEARER_TYPE = "Bearer";
    // OAuth2类型
    public static String OAUTH2_TYPE = "OAuth2";
    // 授权服务器颁发的访问令牌名,该值是必需的
    public static String ACCESS_TOKEN = "access_token";
    // 发行的令牌类型,值不区分大小写。该值是必需的
    public static String TOKEN_TYPE = "token_type";
    // 访问令牌的生命周期(以秒为单位),例如,值“3600”表示访问令牌将在生成响应后的一小时内到期。该值是可选的。
    public static String EXPIRES_IN = "expires_in";
    // 刷新令牌,该值是可选的
    public static String REFRESH_TOKEN = "refresh_token";
    // 访问令牌的范围
    public static String SCOPE = "scope";
    // 令牌序列化程序使用 additionalInformation 映射来导出 OAuth 扩展使用的任何字段
    Map<String, Object> getAdditionalInformation();
    // 省略Getter setter
}

DefaultOAuth2AccessToken

DefaultOAuth2AccessToken 是 OAuth2AccessToken 接口的默认实现类。

public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken {

    private static final long serialVersionUID = 914967629530462926L;
    // 令牌值
    private String value;
    // 到期时间
    private Date expiration;
    // 令牌类型
    private String tokenType = BEARER_TYPE.toLowerCase();
    // 刷新令牌
    private OAuth2RefreshToken refreshToken;
    // 范围
    private Set<String> scope;
    // 序列化字段
    private Map<String, Object> additionalInformation = Collections.emptyMap();
    // 根据提供的值创建访问令牌
    public DefaultOAuth2AccessToken(String value) {
        this.value = value;
    }
    // 查询过期的便捷方法
    public boolean isExpired() {
        return expiration != null && expiration.before(new Date());
    }
    // 将Map转为OAuth2AccessToken 
    public static OAuth2AccessToken valueOf(Map<String, String> tokenParams) {
        DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(tokenParams.get(ACCESS_TOKEN));

        if (tokenParams.containsKey(EXPIRES_IN)) {
            long expiration = 0;
            try {
                expiration = Long.parseLong(String.valueOf(tokenParams.get(EXPIRES_IN)));
            }
            catch (NumberFormatException e) {
                // fall through...
            }
            token.setExpiration(new Date(System.currentTimeMillis() + (expiration * 1000L)));
        }

        if (tokenParams.containsKey(REFRESH_TOKEN)) {
            String refresh = tokenParams.get(REFRESH_TOKEN);
            DefaultOAuth2RefreshToken refreshToken = new DefaultOAuth2RefreshToken(refresh);
            token.setRefreshToken(refreshToken);
        }

        if (tokenParams.containsKey(SCOPE)) {
            Set<String> scope = new TreeSet<String>();
            for (StringTokenizer tokenizer = new StringTokenizer(tokenParams.get(SCOPE), " ,"); tokenizer
                    .hasMoreTokens();) {
                scope.add(tokenizer.nextToken());
            }
            token.setScope(scope);
        }

        if (tokenParams.containsKey(TOKEN_TYPE)) {
            token.setTokenType(tokenParams.get(TOKEN_TYPE));
        }

        return token;
    }

AuthorizationServerTokenServices

在上篇文档中介绍到在 AuthorizationServerEndpointsConfigurer 端点配置类中,有一个 AuthorizationServerTokenServices 属性。

接口

AuthorizationServerTokenServices 接口定义了一些操作使得你可以对令牌进行一些必要的管理,令牌可以被用来 加载身份信息,里面包含了这个令牌的相关权限。

public interface AuthorizationServerTokenServices {
    // 创建与指定凭据OAuth2Authentication关联的访问令牌
    OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
    // 刷新访问令牌
    OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
            throws AuthenticationException;
    // 提供的身份验证密钥存储的访问令牌(如果存在)
    OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

}

实现类

DefaultTokenServices 是 AuthorizationServerTokenServices 的唯一默认实现类。

// 
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
        ConsumerTokenServices, InitializingBean {
    // 刷新令牌的过期时间,默认30天
    private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days.
    // 访问令牌的过期时间,默认12小时
    private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.
    // 是否支持刷新令牌
    private boolean supportRefreshToken = false;
    // 重用刷新令牌
    private boolean reuseRefreshToken = true;
    // 令牌存储
    private TokenStore tokenStore;
    // ClientDetailsService 
    private ClientDetailsService clientDetailsService;
    // 令牌增强器
    private TokenEnhancer accessTokenEnhancer;
    // 认证管理器
    private AuthenticationManager authenticationManager;
    // 创建访问令牌
    @Transactional
    public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
        // 1. tokenStore中获取令牌
        OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
        OAuth2RefreshToken refreshToken = null;
        if (existingAccessToken != null) {
            // 2. 获取令牌有,但是过期了,则移除访问令牌及刷新令牌
            if (existingAccessToken.isExpired()) {
                if (existingAccessToken.getRefreshToken() != null) {
                    refreshToken = existingAccessToken.getRefreshToken();
                    // The token store could remove the refresh token when the
                    // access token is removed, but we want to
                    // be sure...
                    tokenStore.removeRefreshToken(refreshToken);
                }
                tokenStore.removeAccessToken(existingAccessToken);
            }
            else {
                // 3.有令牌未过期,重新存储访问令牌以防身份验证发生变化
                // Re-store the access token in case the authentication has changed
                tokenStore.storeAccessToken(existingAccessToken, authentication);
                return existingAccessToken;
            }
        }

        // Only create a new refresh token if there wasn't an existing one
        // associated with an expired access token.
        // Clients might be holding existing refresh tokens, so we re-use it in
        // the case that the old access token
        // expired.
        // 4. 刷新令牌获取访问令牌为空,则创建
        if (refreshToken == null) {
            refreshToken = createRefreshToken(authentication);
        }
        // But the refresh token itself might need to be re-issued if it has
        // expired.
        else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
            ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
            if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
                refreshToken = createRefreshToken(authentication);
            }
        }
        // 5. 创建并存储令牌
        OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
        tokenStore.storeAccessToken(accessToken, authentication);
        // In case it was modified
        refreshToken = accessToken.getRefreshToken();
        if (refreshToken != null) {
            tokenStore.storeRefreshToken(refreshToken, authentication);
        }
        return accessToken;

    }
    // 刷新访问令牌
    @Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
    public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
            throws AuthenticationException {
        // 当前client不支持刷新,抛出InvalidGrantException
        if (!supportRefreshToken) {
            throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
        }
        // 获取刷新令牌,没有抛出InvalidGrantException
        OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
        if (refreshToken == null) {
            throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
        }
        // 使用刷新令牌,返回新的访问令牌
        OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
        if (this.authenticationManager != null && !authentication.isClientOnly()) {
            // The client has already been authenticated, but the user authentication might be old now, so give it a
            // chance to re-authenticate.
            Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
            user = authenticationManager.authenticate(user);
            Object details = authentication.getDetails();
            authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
            authentication.setDetails(details);
        }
        String clientId = authentication.getOAuth2Request().getClientId();
        if (clientId == null || !clientId.equals(tokenRequest.getClientId())) {
            throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
        }

        // clear out any access tokens already associated with the refresh
        // token.
        tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);

        if (isExpired(refreshToken)) {
            tokenStore.removeRefreshToken(refreshToken);
            throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);
        }

        authentication = createRefreshedAuthentication(authentication, tokenRequest);

        if (!reuseRefreshToken) {
            tokenStore.removeRefreshToken(refreshToken);
            refreshToken = createRefreshToken(authentication);
        }

        OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
        tokenStore.storeAccessToken(accessToken, authentication);
        if (!reuseRefreshToken) {
            tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
        }
        return accessToken;
    }

    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        return tokenStore.getAccessToken(authentication);
    }

    /**
     * Create a refreshed authentication.
     *  创建刷新的身份验证
     * @param authentication The authentication.
     * @param request The scope for the refreshed token.
     * @return The refreshed authentication.
     * @throws InvalidScopeException If the scope requested is invalid or wider than the original scope.
     */
    private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) {
        OAuth2Authentication narrowed = authentication;
        Set<String> scope = request.getScope();
        OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request);
        if (scope != null && !scope.isEmpty()) {
            Set<String> originalScope = clientAuth.getScope();
            if (originalScope == null || !originalScope.containsAll(scope)) {
                throw new InvalidScopeException("Unable to narrow the scope of the client authentication to " + scope
                        + ".", originalScope);
            }
            else {
                clientAuth = clientAuth.narrowScope(scope);
            }
        }
        narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication());
        return narrowed;
    }

    protected boolean isExpired(OAuth2RefreshToken refreshToken) {
        if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
            ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken;
            return expiringToken.getExpiration() == null
                    || System.currentTimeMillis() > expiringToken.getExpiration().getTime();
        }
        return false;
    }

    public OAuth2AccessToken readAccessToken(String accessToken) {
        return tokenStore.readAccessToken(accessToken);
    }

    public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
            InvalidTokenException {
        OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
        if (accessToken == null) {
            throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
        }
        else if (accessToken.isExpired()) {
            tokenStore.removeAccessToken(accessToken);
            throw new InvalidTokenException("Access token expired: " + accessTokenValue);
        }

        OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
        if (result == null) {
            // in case of race condition
            throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
        }
        if (clientDetailsService != null) {
            String clientId = result.getOAuth2Request().getClientId();
            try {
                clientDetailsService.loadClientByClientId(clientId);
            }
            catch (ClientRegistrationException e) {
                throw new InvalidTokenException("Client not valid: " + clientId, e);
            }
        }
        return result;
    }

    public String getClientId(String tokenValue) {
        OAuth2Authentication authentication = tokenStore.readAuthentication(tokenValue);
        if (authentication == null) {
            throw new InvalidTokenException("Invalid access token: " + tokenValue);
        }
        OAuth2Request clientAuth = authentication.getOAuth2Request();
        if (clientAuth == null) {
            throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue);
        }
        return clientAuth.getClientId();
    }

    public boolean revokeToken(String tokenValue) {
        OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
        if (accessToken == null) {
            return false;
        }
        if (accessToken.getRefreshToken() != null) {
            tokenStore.removeRefreshToken(accessToken.getRefreshToken());
        }
        tokenStore.removeAccessToken(accessToken);
        return true;
    }

    private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
        if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
            return null;
        }
        int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
        String value = UUID.randomUUID().toString();
        if (validitySeconds > 0) {
            return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis()
                    + (validitySeconds * 1000L)));
        }
        return new DefaultOAuth2RefreshToken(value);
    }

    private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
        DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
        int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
        if (validitySeconds > 0) {
            token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
        }
        token.setRefreshToken(refreshToken);
        token.setScope(authentication.getOAuth2Request().getScope());

        return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
    }

    /**
     * The access token validity period in seconds
     * 
     * @param clientAuth the current authorization request
     * @return the access token validity period in seconds
     */
    protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
        if (clientDetailsService != null) {
            ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
            Integer validity = client.getAccessTokenValiditySeconds();
            if (validity != null) {
                return validity;
            }
        }
        return accessTokenValiditySeconds;
    }

    /**
     * The refresh token validity period in seconds
     * 
     * @param clientAuth the current authorization request
     * @return the refresh token validity period in seconds
     */
    protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) {
        if (clientDetailsService != null) {
            ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
            Integer validity = client.getRefreshTokenValiditySeconds();
            if (validity != null) {
                return validity;
            }
        }
        return refreshTokenValiditySeconds;
    }

    /**
     * Is a refresh token supported for this client (or the global setting if
     * {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set.
     * 
     * @param clientAuth the current authorization request
     * @return boolean to indicate if refresh token is supported
     */
    protected boolean isSupportRefreshToken(OAuth2Request clientAuth) {
        if (clientDetailsService != null) {
            ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
            return client.getAuthorizedGrantTypes().contains("refresh_token");
        }
        return this.supportRefreshToken;
    }

    /**
     * An access token enhancer that will be applied to a new token before it is saved in the token store.
     * 
     * @param accessTokenEnhancer the access token enhancer to set
     */
    public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) {
        this.accessTokenEnhancer = accessTokenEnhancer;
    }

    /**
     * The validity (in seconds) of the refresh token. If less than or equal to zero then the tokens will be
     * non-expiring.
     * 
     * @param refreshTokenValiditySeconds The validity (in seconds) of the refresh token.
     */
    public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) {
        this.refreshTokenValiditySeconds = refreshTokenValiditySeconds;
    }

    /**
     * The default validity (in seconds) of the access token. Zero or negative for non-expiring tokens. If a client
     * details service is set the validity period will be read from the client, defaulting to this value if not defined
     * by the client.
     * 
     * @param accessTokenValiditySeconds The validity (in seconds) of the access token.
     */
    public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) {
        this.accessTokenValiditySeconds = accessTokenValiditySeconds;
    }

    /**
     * Whether to support the refresh token.
     * 
     * @param supportRefreshToken Whether to support the refresh token.
     */
    public void setSupportRefreshToken(boolean supportRefreshToken) {
        this.supportRefreshToken = supportRefreshToken;
    }

    /**
     * Whether to reuse refresh tokens (until expired).
     * 
     * @param reuseRefreshToken Whether to reuse refresh tokens (until expired).
     */
    public void setReuseRefreshToken(boolean reuseRefreshToken) {
        this.reuseRefreshToken = reuseRefreshToken;
    }

    /**
     * The persistence strategy for token storage.
     * 
     * @param tokenStore the store for access and refresh tokens.
     */
    public void setTokenStore(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    /**
     * An authentication manager that will be used (if provided) to check the user authentication when a token is
     * refreshed.
     * 
     * @param authenticationManager the authenticationManager to set
     */
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    /**
     * The client details service to use for looking up clients (if necessary). Optional if the access token expiry is
     * set globally via {@link #setAccessTokenValiditySeconds(int)}.
     * 
     * @param clientDetailsService the client details service
     */
    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }
}

令牌存储

TokenStore

TokenStore 是 OAuth2 令牌的持久性接口,定义了存储及获取令牌的相关方法。

public interface TokenStore {

    /**
     * Read the authentication stored under the specified token value.
     * 
     * @param token The token value under which the authentication is stored.
     * @return The authentication, or null if none.
     */
    OAuth2Authentication readAuthentication(OAuth2AccessToken token);

    /**
     * Read the authentication stored under the specified token value.
     * 
     * @param token The token value under which the authentication is stored.
     * @return The authentication, or null if none.
     */
    OAuth2Authentication readAuthentication(String token);

    /**
     * Store an access token.
     * 
     * @param token The token to store.
     * @param authentication The authentication associated with the token.
     */
    void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);

    /**
     * Read an access token from the store.
     * 
     * @param tokenValue The token value.
     * @return The access token to read.
     */
    OAuth2AccessToken readAccessToken(String tokenValue);

    /**
     * Remove an access token from the store.
     * 
     * @param token The token to remove from the store.
     */
    void removeAccessToken(OAuth2AccessToken token);

    /**
     * Store the specified refresh token in the store.
     * 
     * @param refreshToken The refresh token to store.
     * @param authentication The authentication associated with the refresh token.
     */
    void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication);

    /**
     * Read a refresh token from the store.
     * 
     * @param tokenValue The value of the token to read.
     * @return The token.
     */
    OAuth2RefreshToken readRefreshToken(String tokenValue);

    /**
     * @param token a refresh token
     * @return the authentication originally used to grant the refresh token
     */
    OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token);

    /**
     * Remove a refresh token from the store.
     * 
     * @param token The token to remove from the store.
     */
    void removeRefreshToken(OAuth2RefreshToken token);

    /**
     * Remove an access token using a refresh token. This functionality is necessary so refresh tokens can't be used to
     * create an unlimited number of access tokens.
     * 
     * @param refreshToken The refresh token.
     */
    void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken);

    /**
     * Retrieve an access token stored against the provided authentication key, if it exists.
     * 
     * @param authentication the authentication key for the access token
     * 
     * @return the access token or null if there was none
     */
    OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

    /**
     * @param clientId the client id to search
     * @param userName the user name to search
     * @return a collection of access tokens
     */
    Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName);
    /**
     * @param clientId the client id to search
     * @return a collection of access tokens
     */
    Collection<OAuth2AccessToken> findTokensByClientId(String clientId);
}

TokenStore 实现类

TokenStore 有以下实现类,每一个对应一种存储方式,可以使用内存、数据库、JWT、Redis 来保存我们的令牌。

默认使用的是 InMemoryTokenStore 来存储,如果用数据库,那么每次 token 服务查询、存储,都需要 SQL 操作,可以使用 JwtTokenStore,将令牌保存在 JWT 中。

使用 JwtTokenStore 存储,会涉及到 JWT 转换,框架也提供了 JwtAccessTokenConverter 类来处理。

它可以把令牌相关的数据进行编码(因此对于后端服务来说,它不需要进行存储,这将是一个重大优势),但是它有一个缺点,那就是撤销一个已经授权令牌将会非常困难,所以它通常用来处理一个生命周期较短的令牌以及撤销刷新令牌(refresh_token)。 另外一个缺点就是这个令牌占用的空间会比较大,如果你加入了比较多用户凭证信息。

JwtAccessTokenConverter 使用 enhance 方法对 token 进行增强。

public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
        Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
        String tokenId = result.getValue();
        // 原先的令牌,存为 jti: token
        if (!info.containsKey(TOKEN_ID)) {
            info.put(TOKEN_ID, tokenId);
        }
        else {
            tokenId = (String) info.get(TOKEN_ID);
        }
        // 设置附加信息
        result.setAdditionalInformation(info);
        // JwtHelper.encode
        result.setValue(encode(result, authentication));
        // 处理刷新令牌
        OAuth2RefreshToken refreshToken = result.getRefreshToken();
        if (refreshToken != null) {
            DefaultOAuth2AccessToken encodedRefreshToken = new DefaultOAuth2AccessToken(accessToken);
            encodedRefreshToken.setValue(refreshToken.getValue());
            // Refresh tokens do not expire unless explicitly of the right type
            encodedRefreshToken.setExpiration(null);
            try {
                Map<String, Object> claims = objectMapper
                        .parseMap(JwtHelper.decode(refreshToken.getValue()).getClaims());
                if (claims.containsKey(TOKEN_ID)) {
                    encodedRefreshToken.setValue(claims.get(TOKEN_ID).toString());
                }
            }
            catch (IllegalArgumentException e) {
            }
            Map<String, Object> refreshTokenInfo = new LinkedHashMap<String, Object>(
                    accessToken.getAdditionalInformation());
            refreshTokenInfo.put(TOKEN_ID, encodedRefreshToken.getValue());
            refreshTokenInfo.put(ACCESS_TOKEN_ID, tokenId);
            encodedRefreshToken.setAdditionalInformation(refreshTokenInfo);
            DefaultOAuth2RefreshToken token = new DefaultOAuth2RefreshToken(
                    encode(encodedRefreshToken, authentication));
            if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
                Date expiration = ((ExpiringOAuth2RefreshToken) refreshToken).getExpiration();
                encodedRefreshToken.setExpiration(expiration);
                token = new DefaultExpiringOAuth2RefreshToken(encode(encodedRefreshToken, authentication), expiration);
            }
            result.setRefreshToken(token);
        }
        return result;
    }

设置 JWT 令牌

通过以上源码分析,我们如果需要使用 JWT 令牌,那么我们需要设置 JWT 存储及转换器,并设置到 AuthorizationServerEndpointsConfigurer 配置中即可。

  1. 注入 TokenStore 及 JwtAccessTokenConverter
@Configuration
public class MyAuthorizationServerConfigurationBean {

    // 令牌存储
    @Bean
    public TokenStore jwtTokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    // Jwt转换器
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123456");
        return converter;
    }
}
  1. AuthorizationServerEndpointsConfigurer 添加 TokenStore 及 JwtAccessTokenConverter
    @Autowired
    private TokenStore jwtTokenStore;
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    // 端点配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置端点允许的请求方式
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        // 配置认证管理器
        endpoints.authenticationManager(authenticationManager);
        // JWT令牌转换器
        endpoints.accessTokenConverter(jwtAccessTokenConverter);
        // JWT 存储令牌
        endpoints.tokenStore(jwtTokenStore);
    }
  1. 使用密码模式访问令牌,返回了 JWT 令牌。

  2. 使用 JwtHelper.decode 方法进行解析,发现可以直接解析出我们的用户及其权限信息,实际这是不安全的。。。而且如果权限很多,那么这个 JWT 将会很长很长。。。

{"exp":1626466719,"user_name":"user","authorities":["ROLE_USER"],"jti":"1e303c89-a6e0-4a44-95e8-dc462060c1b6","client_id":"client","scope":["app"]}