Spring Security+OAuth2认证服务之令牌管理源码分析

一、AuthorizationServerTokenServices接口

在我们自定义的OAuth2的配置类 configure方法中,向 AuthorizationServerEndpointsConfigurer端点配置类添加了一些信息,有的是 AuthorizationServerEndpointsConfigurer中默认的信息。
在这里插入图片描述
比如:AuthorizationServerEndpointsConfigurer类中有一个 tokenServices属性。

在这里插入图片描述
AuthorizationServerTokenServices 接口定义了一些我们可以对令牌进行一些必要的管理的操作方法。令牌被用来加载身份信息,里面包含了这个令牌的相关权限。

在这里插入图片描述
DefaultTokenServices类是AuthorizationServerTokenServices的唯一默认实现类。

二、DefaultTokenServices类

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;

	private ClientDetailsService clientDetailsService;
	// 令牌增强器
	private TokenEnhancer accessTokenEnhancer;
	// 认证管理器
	private AuthenticationManager authenticationManager;
            
    ...        

1、TokenStore令牌存储

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

实现类有个下面几个:
在这里插入图片描述
默认使用的是InMemoryTokenStore来存储,如果用数据库,那么每次token服务查询、存储,都需要SQL操作。这里重点查看 JdbcTokenStore。

1.1 创建并存储令牌

查看 DefaultTokenServices类的 createAccessToken方法。

	@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;

	}

在这里插入图片描述

1.2 刷新令牌

查看 DefaultTokenServices类的 refreshAccessToken方法。

	@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);
		}

        // 1.获取刷新令牌,
		OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
		if (refreshToken == null) {
			throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
		}

        // 2.使用刷新令牌,返回新的访问令牌
		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.
        //3.移除刷新令牌
		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);
		}

        / 4. 创建并存储令牌
		OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
		tokenStore.storeAccessToken(accessToken, authentication);
		if (!reuseRefreshToken) {
			tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
		}
		return accessToken;
	}

1.3 获取令牌的认证信息

查看 DefaultTokenServices类的 getAccessToken方法。
在这里插入图片描述
返回的是 OAuth2AccessToken对象。

2、OAuth2AccessToken接口

OAuth2AccessToken接口定义了 OAuth2令牌的相关结构和属性。

public interface OAuth2AccessToken {

    // 携带令牌访问的前缀
	public static String BEARER_TYPE = "Bearer";

    // OAuth2类型
	public static String OAUTH2_TYPE = "OAuth2";

	/**
     * 授权服务器颁发的访问令牌名,该值是必需的
	 * The access token issued by the authorization server. This value is REQUIRED.
	 */
	public static String ACCESS_TOKEN = "access_token";

	/**发行的令牌类型,值不区分大小写。该值是必需的
	 * The type of the token issued as described in <a
	 * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-7.1">Section 7.1</a>. Value is case insensitive.
	 * This value is REQUIRED.
	 */
	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();

	Set<String> getScope();

	OAuth2RefreshToken getRefreshToken();

	String getTokenType();

	boolean isExpired();

	Date getExpiration();

	int getExpiresIn();

	String getValue();

}

DefaultOAuth2AccessToken类是它的默认的实现类。
在这里插入图片描述

2.1 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());
	}
    	
    public DefaultOAuth2AccessToken(OAuth2AccessToken accessToken) {
		this(accessToken.getValue());
		setAdditionalInformation(accessToken.getAdditionalInformation());
		setRefreshToken(accessToken.getRefreshToken());
		setExpiration(accessToken.getExpiration());
		setScope(accessToken.getScope());
		setTokenType(accessToken.getTokenType());
	}
    
	// 将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;
	}
...
}

通过查看源码,对OAuth2令牌的生成和它相关结构和属性信息有了一定认识。

– 求知若饥,虚心若愚。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值