From e93c6c328a4f09420f7c290d43efc91b2ec0288e Mon Sep 17 00:00:00 2001 From: gaoqr <13665037151@163.com> Date: Wed, 22 Jan 2025 10:17:35 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=99=BB=E5=BD=95=EF=BC=9A?= =?UTF-8?q?=E9=9D=9E=E8=B6=85=E7=AE=A1=E6=94=AF=E6=8C=81=E7=99=BB=E9=99=86?= =?UTF-8?q?=E6=88=90=E5=8A=9F=E5=90=8E=E7=A7=BB=E9=99=A4=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=85=B6=E4=BB=96token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth2/OAuth2AccessTokenRedisDAO.java | 43 ++++++++++++++++++ .../oauth2/OAuth2TokenServiceImpl.java | 44 ++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java index 01a5fb71d..b39def5d2 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java @@ -5,6 +5,8 @@ import com.cf.imes.framework.common.util.collection.CollectionUtils; import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import com.cf.imes.module.system.dal.redis.RedisKeyConstants; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; @@ -15,6 +17,8 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; +import static com.cf.imes.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN; + /** * {@link OAuth2AccessTokenDO} 的 RedisDAO * @@ -26,11 +30,27 @@ public class OAuth2AccessTokenRedisDAO { @Resource private StringRedisTemplate stringRedisTemplate; + /** + * 不带key获取 + * + * @param accessToken + * @return + */ public OAuth2AccessTokenDO get(String accessToken) { String redisKey = formatKey(accessToken); return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class); } + /** + * 带key获取,oauth2_access_token:xxxx + * + * @param accessToken + * @return + */ + public OAuth2AccessTokenDO getWithKey(String accessToken) { + return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(accessToken), OAuth2AccessTokenDO.class); + } + public void set(OAuth2AccessTokenDO accessTokenDO) { String redisKey = formatKey(accessTokenDO.getAccessToken()); // 清理多余字段,避免缓存 @@ -42,11 +62,26 @@ public class OAuth2AccessTokenRedisDAO { } } + /** + * 不带key删除 + * + * @param accessToken + */ public void delete(String accessToken) { String redisKey = formatKey(accessToken); stringRedisTemplate.delete(redisKey); } + /** + * 带key删除,oauth2_access_token:xxxx + * + * @param accessToken + * @return + */ + public void deleteWithKey(String accessToken) { + stringRedisTemplate.delete(accessToken); + } + public void deleteList(Collection accessTokens) { List redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey); stringRedisTemplate.delete(redisKeys); @@ -67,4 +102,12 @@ public class OAuth2AccessTokenRedisDAO { return String.format(RedisKeyConstants.OAUTH2_ACCESS_TOKEN, accessToken); } + /** + * 构建token游标,使用时轮训一次获取两百个token + * + * @return + */ + public Cursor scan(){ + return stringRedisTemplate.scan(ScanOptions.scanOptions().match(String.format(OAUTH2_ACCESS_TOKEN, "*")).count(200).build()); + } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java index 469f104e7..697c97da6 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -17,12 +17,17 @@ import com.cf.imes.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper; import com.cf.imes.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper; import com.cf.imes.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO; import com.cf.imes.module.system.service.organ.OrganService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.Cursor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception0; @@ -34,6 +39,7 @@ import static com.cf.imes.framework.common.util.collection.CollectionUtils.conve * @author 晨丰科技 */ @Service +@Slf4j public class OAuth2TokenServiceImpl implements OAuth2TokenService { @Resource @@ -182,8 +188,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { // 在Redis中新增Token,除去刷新Token字段 private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2AccessTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { - OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) - .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()) + String currentToken = generateAccessToken(); + Long userId = refreshTokenDO.getUserId(); + OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(currentToken) + .setUserId(userId).setUserType(refreshTokenDO.getUserType()) .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes()) .setRefreshToken(refreshTokenDO.getRefreshToken()) .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds())) @@ -193,9 +201,41 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { .setDeptId(refreshTokenDO.getDeptId()); // 记录到 Redis 中 oauth2AccessTokenRedisDAO.set(accessTokenDO); + // 异步踢出其他token + CompletableFuture.runAsync(() -> { + kickOut(userId, currentToken); + }).exceptionally(e -> { + log.error("[OAuth2TokenServiceImpl] redis 清空token失败, 用户id:{}, 异常:{}", userId, e); + return null; + }); return accessTokenDO; } + /** + * 踢出除本次userId登录产生token以外的所有token(不处理超管的超管除外) + * + * @param userId + * @param currentToken + */ + public void kickOut(Long userId, String currentToken) { + List matchKeys = new ArrayList<>(); + Cursor cursor = oauth2AccessTokenRedisDAO.scan(); + while (cursor.hasNext()) { + matchKeys.add(cursor.next()); + } + cursor.close(); + if (CollUtil.isNotEmpty(matchKeys)) { + for (String key : matchKeys) { + // 获取key下的用户信息 + OAuth2AccessTokenDO oAuth2AccessTokenDO = oauth2AccessTokenRedisDAO.getWithKey(key); + if (ObjectUtil.equal(userId, oAuth2AccessTokenDO.getUserId()) && !StringUtils.equals(currentToken, oAuth2AccessTokenDO.getAccessToken()) && Boolean.FALSE.equals(oAuth2AccessTokenDO.getIsSupAdmin())) { + // 用户id匹配、非当次登录token、非超管 -> 删除redis中的token缓存 + oauth2AccessTokenRedisDAO.deleteWithKey(key); + } + } + } + } + private static String generateAccessToken() {