Skip to content

Commit a7c3dba

Browse files
committed
feat: 支持配置单个账号最大 token 数量(默认 1 即不支持重复登录)
1 parent b4a2ed6 commit a7c3dba

File tree

4 files changed

+58
-5
lines changed

4 files changed

+58
-5
lines changed

framework/framework_base/src/main/java/com/github/cadecode/uniboot/framework/base/config/SecurityConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,5 +193,12 @@ public static class TokenConfig {
193193
* 密钥
194194
*/
195195
private String secret;
196+
197+
/**
198+
* 单个账号最多允许几个 token(大于 0)
199+
* 为 1 时即不允许多次登录同时在线
200+
*/
201+
private Integer maxCount = 1;
202+
196203
}
197204
}

framework/framework_base/src/main/java/com/github/cadecode/uniboot/framework/base/security/strategy/RedisTokenAuthStrategyImpl.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,18 @@ public void handler(HttpServletRequest request, HttpServletResponse response, Fi
4242
return;
4343
}
4444
// 查询 redis 中 token
45-
String loginUserKey = KeyGeneUtil.key(KeyPrefixConst.LOGIN_USER, uuidToken);
46-
SysUserDetails sysUserDetails = RedisUtil.get(loginUserKey, SysUserDetails.class);
45+
String loginUserTokenKey = KeyGeneUtil.key(KeyPrefixConst.LOGIN_USER, uuidToken);
46+
SysUserDetails sysUserDetails = RedisUtil.get(loginUserTokenKey, SysUserDetails.class);
4747
// redis 中用户不存在
4848
if (Objects.isNull(sysUserDetails)) {
4949
writeResponse(response, AuthErrorEnum.TOKEN_EXPIRED, requestURI);
5050
return;
5151
}
5252
// 用户存在,刷新过期时间
53-
RedisUtil.expire(loginUserKey, SecurityUtil.getExpiration(), TimeUnit.SECONDS);
53+
RedisUtil.expire(loginUserTokenKey, SecurityUtil.getExpiration(), TimeUnit.SECONDS);
54+
String loginUsernameKey = KeyGeneUtil.key(KeyPrefixConst.LOGIN_USER, sysUserDetails.getUsername());
55+
RedisUtil.expire(loginUsernameKey, SecurityUtil.getExpiration(), TimeUnit.SECONDS);
56+
5457
ServletUtil.addCookie(response, HttpConst.HEAD_TOKEN, uuidToken, SecurityUtil.getExpiration().intValue());
5558
// 设置 AuthenticationToken
5659
setAuthentication(request, sysUserDetails);

framework/framework_base/src/main/java/com/github/cadecode/uniboot/framework/base/util/SecurityUtil.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public class SecurityUtil implements InitializingBean {
3232

3333
// 从 SecurityConfig 获取 token 配置
3434

35+
public static SecurityProperties properties() {
36+
return PROPERTIES;
37+
}
38+
3539
public static Long getExpiration() {
3640
return PROPERTIES.getTokenConfig().getExpiration();
3741
}
@@ -40,6 +44,10 @@ public static String getSecret() {
4044
return PROPERTIES.getTokenConfig().getSecret();
4145
}
4246

47+
public static Integer getMaxCount() {
48+
return PROPERTIES.getTokenConfig().getMaxCount();
49+
}
50+
4351
/**
4452
* 从request对象解析token,cookie or header
4553
*

framework/framework_svc/src/main/java/com/github/cadecode/uniboot/framework/svc/security/RedisLoginSuccessHandleService.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.github.cadecode.uniboot.framework.svc.security;
22

3+
import cn.hutool.core.util.ObjUtil;
4+
import com.fasterxml.jackson.core.type.TypeReference;
35
import com.github.cadecode.uniboot.common.core.extension.strategy.StrategyContext;
46
import com.github.cadecode.uniboot.common.core.web.response.ApiResult;
57
import com.github.cadecode.uniboot.common.plugin.cache.util.KeyGeneUtil;
@@ -15,7 +17,12 @@
1517

1618
import javax.servlet.http.HttpServletRequest;
1719
import javax.servlet.http.HttpServletResponse;
20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.Objects;
1824
import java.util.concurrent.TimeUnit;
25+
import java.util.stream.Collectors;
1926

2027
/**
2128
* 登录成功处理器
@@ -35,11 +42,39 @@ public ApiResult<SysUserDetails> getResult(HttpServletRequest request, HttpServl
3542
// token 放在请求头
3643
response.addHeader(HttpConst.HEAD_TOKEN, uuidToken);
3744
// 生成存放登录信息的 redis key
38-
String loginUserKey = KeyGeneUtil.key(KeyPrefixConst.LOGIN_USER, uuidToken);
39-
RedisUtil.set(loginUserKey, sysUserDetails, SecurityUtil.getExpiration(), TimeUnit.SECONDS);
45+
String loginUserTokenKey = KeyGeneUtil.key(KeyPrefixConst.LOGIN_USER, uuidToken);
46+
String loginUsernameKey = KeyGeneUtil.key(KeyPrefixConst.LOGIN_USER, sysUserDetails.getUsername());
47+
// 获取当前账号 token 列表
48+
List<String> tokenList = getTokenList(loginUsernameKey, loginUserTokenKey);
49+
RedisUtil.set(loginUserTokenKey, sysUserDetails, SecurityUtil.getExpiration(), TimeUnit.SECONDS);
50+
RedisUtil.set(loginUsernameKey, tokenList, SecurityUtil.getExpiration(), TimeUnit.SECONDS);
4051
return ApiResult.ok(sysUserDetails).path(FrameSecurityConfig.LOGOUT_URL);
4152
}
4253

54+
private List<String> getTokenList(String loginUsernameKey, String loginUserTokenKey) {
55+
List<String> tokenList = RedisUtil.get(loginUsernameKey, new TypeReference<ArrayList<String>>() {});
56+
if (ObjUtil.isEmpty(tokenList)) {
57+
return new ArrayList<>(Collections.singletonList(loginUserTokenKey));
58+
}
59+
// 当配置了最大 token 数
60+
Integer maxTokenCount = SecurityUtil.getMaxCount();
61+
if (Objects.nonNull(maxTokenCount) && maxTokenCount > 0) {
62+
tokenList.add(loginUserTokenKey);
63+
if (tokenList.size() > maxTokenCount) {
64+
// 清理超过限制的 token
65+
tokenList.stream()
66+
.limit(tokenList.size() - maxTokenCount)
67+
.forEach(RedisUtil::del);
68+
tokenList = tokenList.stream()
69+
.skip(tokenList.size() - maxTokenCount)
70+
.collect(Collectors.toList());
71+
}
72+
} else {
73+
tokenList = new ArrayList<>(Collections.singletonList(loginUserTokenKey));
74+
}
75+
return tokenList;
76+
}
77+
4378
@Override
4479
public boolean supports(StrategyContext delimiter) {
4580
return delimiter.getStrategyType() == AuthModelEnum.REDIS;

0 commit comments

Comments
 (0)