【Spring Security】 DefaultTokenServices
【Spring Security】 DefaultTokenServices
Metadata
title: 【Spring Security】 DefaultTokenServices
date: 2023-02-05 15:44
tags:
- 行动阶段/完成
- 主题场景/组件
- 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
- 细化主题/Module/SpringSecurity
categories:
- SpringSecurity
keywords:
- SpringSecurity
description: 【Spring Security】 DefaultTokenServices
【Spring Security】 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 值