Spring Security系列(29)- Spring Security Oauth2之DefaultTokenServices源码解析及使用详解

DefaultTokenServices源码解析

DefaultTokenServices是默认的令牌服务类,其实现了以下四个接口:

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
		ConsumerTokenServices, InitializingBean 

这些接口的主要作用如下:

  • AuthorizationServerTokenServices:授权服务器的令牌服务类
  • ResourceServerTokenServices:资源服务器的令牌服务类
  • ConsumerTokenServices:注销令牌服务类
  • InitializingBean :Bean后置处理

该类的属性说明如下:

	// 刷新令牌过期时间 默认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. 实现InitializingBean 接口

实现InitializingBean,主要是对令牌存储进行检查,必须设置相应的存储类。

	public void afterPropertiesSet() throws Exception {
    
    
		Assert.notNull(tokenStore, "tokenStore must be set");
	}

2. 实现 AuthorizationServerTokenServices接口

实现其createAccessToken 方法,用于创建令牌对象:

	@Transactional
	public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
    
    
		// 1. 首先判断当前用户是否已存在访问令牌
		OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
		OAuth2RefreshToken refreshToken = null;
		// 2. 如果已存在,判断是否过期,已过期则直接删除,没过期则重新存储并返回之前的令牌。
		if (existingAccessToken != null) {
    
    
			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 {
    
    
				// Re-store the access token in case the authentication has changed
				tokenStore.storeAccessToken(existingAccessToken, authentication);
				return existingAccessToken;
			}
		}

		// 3. 没有刷新令牌则创建刷新令牌 
		if (refreshToken == null) {
    
    
			refreshToken = createRefreshToken(authentication);
		}
		// 4. 如果刷新令牌过期了,创建新的刷新令牌
		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);
		// 6. 有刷新令牌则存贮,并返回令牌对象
		refreshToken = accessToken.getRefreshToken();
		if (refreshToken != null) {
    
    
			tokenStore.storeRefreshToken(refreshToken, authentication);
		}
		return accessToken;
	}

实现其refreshAccessToken方法,用于刷新访问令牌:

	@Transactional(noRollbackFor={
    
    InvalidTokenException.class, InvalidGrantException.class})
	public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
			throws AuthenticationException {
    
    
		// 1. 不支持刷新 当前令牌,则抛出异常
		if (!supportRefreshToken) {
    
    
			throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
		}
		// 2. 获取存储中的刷新令牌对象
		OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
		if (refreshToken == null) {
    
    
			throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
		}	
		// 3. 通过刷新令牌获取认证信息
		OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
		// 4. 如果设置了认证管理器,则会重新对进行认证
		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);
		}
		// 5. 创建新的访问令牌并返回,如果没设置重用,则会创建新的刷新令牌并返回
		OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
		tokenStore.storeAccessToken(accessToken, authentication);
		if (!reuseRefreshToken) {
    
    
			tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
		}
		return accessToken;
	}

实现其getAccessToken方法,用于根据认证对象获取访问令牌:

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

3. 实现 ResourceServerTokenServices接口

实现其loadAuthentication方法,用于获取认证对象:

	public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
			InvalidTokenException {
    
    
		// 1. 获取访问令牌对象
		OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
		if (accessToken == null) {
    
    
			throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
		}
		// 2. 过期则直接删除,并抛出异常
		else if (accessToken.isExpired()) {
    
    
			tokenStore.removeAccessToken(accessToken);
			throw new InvalidTokenException("Access token expired: " + accessTokenValue);
		}
		// 3. 获取认证信息
		OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
		if (result == null) {
    
    
			// in case of race condition
			throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
		}
		// 4. 重新查询客户端信息,没有改客户端信息,则抛出异常
		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;
	}

实现其readAccessToken方法,用于获取访问令牌对象:

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

4. 实现ConsumerTokenServices 接口

实现其revokeToken方法,用于注销:

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

应用案例

在了解了DefaultTokenServices 的源码之后,我们就可是对令牌进行更近一步的自定义。

案例1. 返回自定义Token ID

源码分析

在申请令牌的时候,返回的令牌,其实只是一个令牌值(也可以理解为ID ),还有其他令牌信息是存贮在持久化组件中的。
在这里插入图片描述
比如之前使用Redis 存储,其信息展示如下:
在这里插入图片描述
首先我们看下令牌对象DefaultOAuth2AccessToken源码:

	// token值 
	private String value;
	// 什么时候过期
	private Date expiration;
	// 类型 默认Bearer 
	private String tokenType = BEARER_TYPE.toLowerCase();
	// 刷新令牌
	private OAuth2RefreshToken refreshToken;
	// 范围
	private Set<String> scope;
	// 自定义信息
	private Map<String, Object> additionalInformation = Collections.emptyMap();

在DefaultTokenServices中创建令牌对象的源码如下:

	private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
    
    
		// 1. 使用UUID工具类 生成value值
		DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
		// 2. 获取客户端设置的过期时间
		int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
		if (validitySeconds > 0) {
    
    
			token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
		}
		// 3. 设置刷新令牌 、 范围
		token.setRefreshToken(refreshToken);
		token.setScope(authentication.getOAuth2Request().getScope());
		// 4. 令牌增强
		return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
	}

在上面代码可以看到,使用的是UUID生成的随机字符串,那么我们修改这个代码,就可以使用自定义的生成方式了。

自定义令牌值实现方案

自定义MyDefaultTokenServices,将MyDefaultTokenServices中的代码都复制过来,添加一个生成value 值的方法,然后将createRefreshToken、createAccessToken使用此方法生成令牌值。

    private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
    
    
        if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
    
    
            return null;
        }
        int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
        // 自定义令牌值生成策略
        String value = createTokenValue(authentication);
        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(createTokenValue(authentication));
        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;
    }

    /**
     * 自定义令牌值生成策略
     */
    private String createTokenValue(OAuth2Authentication authentication) {
    
    
        String clientId = authentication.getOAuth2Request().getClientId();
        return UUID.randomUUID() + "_" + clientId;
    }

配置一个MyDefaultTokenServices Bean对象:


    @Bean
    @Primary
    public MyDefaultTokenServices defaultTokenServices(RedisConnectionFactory connectionFactory) {
    
    
        MyDefaultTokenServices resourceServerTokenServices = new MyDefaultTokenServices();
        resourceServerTokenServices.setTokenStore(tokenStore(connectionFactory));
        return resourceServerTokenServices;
    }

在授权服务器配置类中,端点添加自定义的DefaultTokenServices:

    // 端点配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    
    
        // 省略其他。。。
        endpoints.pathMapping("/oauth/token","/custom/token");
        // 添加自定义Token Services
        endpoints.tokenServices(myDefaultTokenServices);
    }

测试:可以看到采用自定义的方式生成的token值
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43437874/article/details/121403591