基于Redis实现双加密Token的示例代码
作者:Asthenia0412
在现代分布式系统中,Token管理是身份验证和授权的核心部分,本文将深入分析一个基于Redis的Token管理实现,探讨其设计思路、关键代码逻辑以及实现细节,通过对源码的逐层剖析,帮助读者更好地理解Token管理的实现原理,需要的朋友可以参考下
1. 核心设计思路
TokenStore类的核心设计目标是实现一个高效、安全的Token管理系统,主要功能包括:
- Token的生成与存储:用户登录时生成
accessToken和refreshToken,并将其存储在Redis中。 - Token的刷新:通过
refreshToken获取新的accessToken。 - Token的删除:删除用户的所有Token,通常用于用户注销或管理员禁用用户。
- 用户信息的获取:通过
accessToken获取用户的详细信息。
为了实现这些功能,TokenStore类依赖Redis作为存储介质,利用Redis的高性能和丰富的数据结构(如String、Set)来管理Token和用户信息。
2. 关键代码逻辑分析
2.1 Token的生成与存储
在用户登录时,系统会生成一个accessToken和一个refreshToken,并将它们存储在Redis中。以下是storeAccessToken方法的核心逻辑:
public TokenInfoBO storeAccessToken(UserInfoInTokenBO userInfoInToken) {
TokenInfoBO tokenInfoBO = new TokenInfoBO();
String accessToken = IdUtil.simpleUUID(); // 生成随机的accessToken
String refreshToken = IdUtil.simpleUUID(); // 生成随机的refreshToken
tokenInfoBO.setUserInfoInToken(userInfoInToken);
tokenInfoBO.setExpiresIn(getExpiresIn(userInfoInToken.getSysType())); // 设置Token过期时间
String uidToAccessKeyStr = getUidToAccessKey(getApprovalKey(userInfoInToken)); // 生成uid_to_access的Redis Key
String accessKeyStr = getAccessKey(accessToken); // 生成access_token的Redis Key
String refreshToAccessKeyStr = getRefreshToAccessKey(refreshToken); // 生成refresh_to_access的Redis Key
// 将新的Token加入现有Token列表
List<String> existsAccessTokens = new ArrayList<>();
existsAccessTokens.add(accessToken + StrUtil.COLON + refreshToken);
// 检查并清理过期的Token
Long size = redisTemplate.opsForSet().size(uidToAccessKeyStr);
if (size != null && size != 0) {
List<String> tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidToAccessKeyStr, size);
if (tokenInfoBoList != null) {
for (String accessTokenWithRefreshToken : tokenInfoBoList) {
String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON);
String accessTokenData = accessTokenWithRefreshTokenArr[0];
if (BooleanUtil.isTrue(stringRedisTemplate.hasKey(getAccessKey(accessTokenData)))) {
existsAccessTokens.add(accessTokenWithRefreshToken);
}
}
}
}
// 使用Redis管道批量操作
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
long expiresIn = tokenInfoBO.getExpiresIn();
byte[] uidKey = uidToAccessKeyStr.getBytes(StandardCharsets.UTF_8);
byte[] refreshKey = refreshToAccessKeyStr.getBytes(StandardCharsets.UTF_8);
byte[] accessKey = accessKeyStr.getBytes(StandardCharsets.UTF_8);
// 将Token列表存入Redis
for (String existsAccessToken : existsAccessTokens) {
connection.sAdd(uidKey, existsAccessToken.getBytes(StandardCharsets.UTF_8));
}
// 设置uid_to_access的过期时间
connection.expire(uidKey, expiresIn);
// 存储refresh_to_access的映射
connection.setEx(refreshKey, expiresIn, accessToken.getBytes(StandardCharsets.UTF_8));
// 存储access_token对应的用户信息
connection.setEx(accessKey, expiresIn, Objects.requireNonNull(redisSerializer.serialize(userInfoInToken)));
return null;
});
// 返回加密后的Token
tokenInfoBO.setAccessToken(encryptToken(accessToken, userInfoInToken.getSysType()));
tokenInfoBO.setRefreshToken(encryptToken(refreshToken, userInfoInToken.getSysType()));
return tokenInfoBO;
}
关键点分析:
- Token生成:使用
IdUtil.simpleUUID()生成随机的accessToken和refreshToken,确保Token的唯一性。 - Redis数据结构:
uid_to_access:使用Set数据结构存储用户的所有Token,方便管理和清理。refresh_to_access:使用String数据结构存储refreshToken到accessToken的映射。access_token:使用String数据结构存储accessToken对应的用户信息。
- 过期时间管理:根据用户类型(
sysType)设置不同的Token过期时间,普通用户和管理员的Token过期时间不同。 - Redis管道操作:通过
executePipelined方法批量执行Redis操作,减少网络开销,提升性能。
2.2 Token的刷新
当accessToken过期时,用户可以通过refreshToken获取新的accessToken。以下是refreshToken方法的核心逻辑:
public ServerResponseEntity<TokenInfoBO> refreshToken(String refreshToken) {
if (StrUtil.isBlank(refreshToken)) {
return ServerResponseEntity.showFailMsg("refreshToken is blank");
}
ServerResponseEntity<String> decryptTokenEntity = decryptToken(refreshToken); // 解密refreshToken
if (!decryptTokenEntity.isSuccess()) {
return ServerResponseEntity.transform(decryptTokenEntity);
}
String realRefreshToken = decryptTokenEntity.getData();
String accessToken = stringRedisTemplate.opsForValue().get(getRefreshToAccessKey(realRefreshToken)); // 获取对应的accessToken
if (StrUtil.isBlank(accessToken)) {
return ServerResponseEntity.showFailMsg("refreshToken 已过期");
}
ServerResponseEntity<UserInfoInTokenBO> userInfoByAccessTokenEntity = getUserInfoByAccessToken(accessToken, false);
if (!userInfoByAccessTokenEntity.isSuccess()) {
return ServerResponseEntity.showFailMsg("refreshToken 已过期");
}
UserInfoInTokenBO userInfoInTokenBO = userInfoByAccessTokenEntity.getData();
// 删除旧的refresh_token和access_token
stringRedisTemplate.delete(getRefreshToAccessKey(realRefreshToken));
stringRedisTemplate.delete(getAccessKey(accessToken));
// 生成新的Token
TokenInfoBO tokenInfoBO = storeAccessToken(userInfoInTokenBO);
return ServerResponseEntity.success(tokenInfoBO);
}
关键点分析:
- Token解密:通过
decryptToken方法解密refreshToken,确保Token的安全性。 - Token映射:通过
refresh_to_access的映射关系,找到对应的accessToken。 - Token清理:删除旧的
refreshToken和accessToken,防止Token被重复使用。 - 新Token生成:调用
storeAccessToken方法生成新的Token。
2.3 Token的删除
在某些情况下(如用户注销或管理员禁用用户),需要删除用户的所有Token。以下是deleteAllToken方法的核心逻辑:
public void deleteAllToken(String appId, Long uid) {
String uidKey = getUidToAccessKey(getApprovalKey(appId, uid)); // 生成uid_to_access的Redis Key
Long size = redisTemplate.opsForSet().size(uidKey);
if (size == null || size == 0) {
return;
}
List<String> tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidKey, size);
if (CollUtil.isEmpty(tokenInfoBoList)) {
return;
}
// 遍历并删除所有Token
for (String accessTokenWithRefreshToken : tokenInfoBoList) {
String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON);
String accessToken = accessTokenWithRefreshTokenArr[0];
String refreshToken = accessTokenWithRefreshTokenArr[1];
redisTemplate.delete(getRefreshToAccessKey(refreshToken));
redisTemplate.delete(getAccessKey(accessToken));
}
redisTemplate.delete(uidKey); // 删除uid_to_access的Key
}
关键点分析:
- 批量删除:通过
uid_to_access的Set数据结构,获取用户的所有Token并批量删除。 - 清理映射关系:删除
refresh_to_access和access_token的映射关系,确保Token完全失效。
3. 总结
通过对TokenStore类的深入分析,我们可以看到其设计思路清晰,代码逻辑严谨。通过Redis的高效存储和丰富的数据结构,实现了Token的生成、刷新、删除以及用户信息的获取。这种设计不仅保证了系统的安全性,还提升了系统的性能和可扩展性。
希望本文的源码分析能够帮助你更好地理解Token管理的实现原理。
到此这篇关于基于Redis实现双加密Token的示例代码的文章就介绍到这了,更多相关Redis双加密Token内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
