系统登录:非超管支持登陆成功后移除当前用户其他token

This commit is contained in:
gaoqr
2025-01-22 10:17:35 +08:00
parent b70c101a1d
commit e93c6c328a
2 changed files with 85 additions and 2 deletions
@@ -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.framework.common.util.json.JsonUtils;
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants; 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.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@@ -15,6 +17,8 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.cf.imes.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
/** /**
* {@link OAuth2AccessTokenDO} 的 RedisDAO * {@link OAuth2AccessTokenDO} 的 RedisDAO
* *
@@ -26,11 +30,27 @@ public class OAuth2AccessTokenRedisDAO {
@Resource @Resource
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
/**
* 不带key获取
*
* @param accessToken
* @return
*/
public OAuth2AccessTokenDO get(String accessToken) { public OAuth2AccessTokenDO get(String accessToken) {
String redisKey = formatKey(accessToken); String redisKey = formatKey(accessToken);
return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class); 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) { public void set(OAuth2AccessTokenDO accessTokenDO) {
String redisKey = formatKey(accessTokenDO.getAccessToken()); String redisKey = formatKey(accessTokenDO.getAccessToken());
// 清理多余字段,避免缓存 // 清理多余字段,避免缓存
@@ -42,11 +62,26 @@ public class OAuth2AccessTokenRedisDAO {
} }
} }
/**
* 不带key删除
*
* @param accessToken
*/
public void delete(String accessToken) { public void delete(String accessToken) {
String redisKey = formatKey(accessToken); String redisKey = formatKey(accessToken);
stringRedisTemplate.delete(redisKey); stringRedisTemplate.delete(redisKey);
} }
/**
* 带key删除,oauth2_access_token:xxxx
*
* @param accessToken
* @return
*/
public void deleteWithKey(String accessToken) {
stringRedisTemplate.delete(accessToken);
}
public void deleteList(Collection<String> accessTokens) { public void deleteList(Collection<String> accessTokens) {
List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey); List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey);
stringRedisTemplate.delete(redisKeys); stringRedisTemplate.delete(redisKeys);
@@ -67,4 +102,12 @@ public class OAuth2AccessTokenRedisDAO {
return String.format(RedisKeyConstants.OAUTH2_ACCESS_TOKEN, accessToken); return String.format(RedisKeyConstants.OAUTH2_ACCESS_TOKEN, accessToken);
} }
/**
* 构建token游标,使用时轮训一次获取两百个token
*
* @return
*/
public Cursor<String> scan(){
return stringRedisTemplate.scan(ScanOptions.scanOptions().match(String.format(OAUTH2_ACCESS_TOKEN, "*")).count(200).build());
}
} }
@@ -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.mysql.oauth2.OAuth2RefreshTokenMapper;
import com.cf.imes.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO; import com.cf.imes.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;
import com.cf.imes.module.system.service.organ.OrganService; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception0; 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 晨丰科技 * @author 晨丰科技
*/ */
@Service @Service
@Slf4j
public class OAuth2TokenServiceImpl implements OAuth2TokenService { public class OAuth2TokenServiceImpl implements OAuth2TokenService {
@Resource @Resource
@@ -182,8 +188,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
// 在Redis中新增Token,除去刷新Token字段 // 在Redis中新增Token,除去刷新Token字段
private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2AccessTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2AccessTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) String currentToken = generateAccessToken();
.setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()) Long userId = refreshTokenDO.getUserId();
OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(currentToken)
.setUserId(userId).setUserType(refreshTokenDO.getUserType())
.setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes()) .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes())
.setRefreshToken(refreshTokenDO.getRefreshToken()) .setRefreshToken(refreshTokenDO.getRefreshToken())
.setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds())) .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()))
@@ -193,9 +201,41 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
.setDeptId(refreshTokenDO.getDeptId()); .setDeptId(refreshTokenDO.getDeptId());
// 记录到 Redis 中 // 记录到 Redis 中
oauth2AccessTokenRedisDAO.set(accessTokenDO); oauth2AccessTokenRedisDAO.set(accessTokenDO);
// 异步踢出其他token
CompletableFuture.runAsync(() -> {
kickOut(userId, currentToken);
}).exceptionally(e -> {
log.error("[OAuth2TokenServiceImpl] redis 清空token失败, 用户id:{}, 异常:{}", userId, e);
return null;
});
return accessTokenDO; return accessTokenDO;
} }
/**
* 踢出除本次userId登录产生token以外的所有token(不处理超管的超管除外)
*
* @param userId
* @param currentToken
*/
public void kickOut(Long userId, String currentToken) {
List<String> matchKeys = new ArrayList<>();
Cursor<String> 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() { private static String generateAccessToken() {