feat(iam): 打通会话管理配置到登录链路

TokenService.doSaLogin 读取 PlatformSessionManagementConfig 并应用: 在线时长->timeout, 活跃超时->activeTimeout, 三种并发策略(KICK_OLDEST/DENY_NEW/NEW_SESSION); enabled=false时回退yml默认; 新增activeTimeoutHours字段与concurrentLimitExceeded文案
This commit is contained in:
DaxPay Dev
2026-07-12 11:41:10 +08:00
parent cfad541867
commit 81a51bef5b
6 changed files with 54 additions and 2 deletions

View File

@@ -26,5 +26,6 @@
"tokenTimeout": "Session expired, please log in again",
"beReplaced": "Account signed in on another device",
"kickOut": "Session no longer valid, please log in again",
"tokenFreeze": "Authentication credential frozen"
"tokenFreeze": "Authentication credential frozen",
"concurrentLimitExceeded": "Concurrent login limit reached, please sign out from another device first"
}

View File

@@ -26,5 +26,6 @@
"tokenTimeout": "登录已过期,请重新登录",
"beReplaced": "账号已在其他设备登录",
"kickOut": "登录状态已失效,请重新登录",
"tokenFreeze": "登录凭证已被冻结"
"tokenFreeze": "登录凭证已被冻结",
"concurrentLimitExceeded": "并发登录数已达上限,请先退出其他设备"
}

View File

@@ -19,6 +19,7 @@ import cn.daxpay.open.platform.iam.exception.auth.ApplicationNotFoundException;
import cn.daxpay.open.platform.iam.result.user.UserInfoResult;
import cn.daxpay.open.platform.iam.service.twofactor.UserTwoFactorService;
import cn.daxpay.open.platform.iam.service.user.UserQueryService;
import cn.daxpay.open.platform.system.entity.config.platform.security.PlatformSessionManagementConfig;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
@@ -56,6 +57,8 @@ public class TokenService {
private final UserQueryService userQueryService;
private final IamSecurityConfigService iamSecurityConfigService;
/// 登录
public String login(HttpServletRequest request, HttpServletResponse response) {
AuthInfoResult authInfoResult;
@@ -197,6 +200,8 @@ public class TokenService {
var saLoginModel = new SaLoginParameter()
.setDeviceType(clientCode)
.setIsLastingCookie(true);
// 应用会话管理配置(在线时长/活跃超时/并发策略)
this.applySessionConfig(saLoginModel, authInfoResult.getId());
authInfoResult.setClient(clientCode)
.setLoginType(loginType);
@@ -206,6 +211,43 @@ public class TokenService {
session.set(CommonCode.USER, userDetail);
}
/// 应用会话管理配置到 Sa-Token 登录参数
/// 配置未启用时跳过, 继续使用 application.yml 中的静态默认值
private void applySessionConfig(SaLoginParameter model, Object userId) {
PlatformSessionManagementConfig config = iamSecurityConfigService.getSessionManagement();
if (config == null || !Boolean.TRUE.equals(config.getEnabled())) {
return;
}
// 在线时长 -> token 固定有效期(秒)
if (config.getMaxOnlineHours() != null && config.getMaxOnlineHours() > 0) {
model.setTimeout(config.getMaxOnlineHours() * 3600L);
}
// 活跃超时 -> 无操作超时(秒), 0或null表示不限制
if (config.getActiveTimeoutHours() != null && config.getActiveTimeoutHours() > 0) {
model.setActiveTimeout(config.getActiveTimeoutHours() * 3600L);
}
// 并发登录策略
Integer max = config.getMaxConcurrentSessions();
String strategy = config.getConcurrentStrategy();
if ("KICK_OLDEST".equals(strategy) && max != null && max > 0) {
// 允许并发, 超出上限时 Sa-Token 自动注销最早的会话
model.setIsConcurrent(true).setMaxLoginCount(max);
}
else if ("DENY_NEW".equals(strategy) && max != null && max > 0) {
// 登录前预检: 已达上限则拒绝新登录
int current = StpUtil.getTokenValueListByLoginId(userId).size();
if (current >= max) {
throw new LoginFailureException("error.auth.concurrentLimitExceeded");
}
// 不设 maxLoginCount, 保持"拒绝"语义, 避免触发 Sa-Token 自动踢旧
model.setIsConcurrent(true);
}
else {
// NEW_SESSION 或未配置策略: 允许并发, 不限制数量
model.setIsConcurrent(true);
}
}
/// 退出
public void logout() {
StpUtil.logout();

View File

@@ -13,6 +13,8 @@ public class PlatformSessionManagementConfig {
private Boolean enabled;
/// 最大在线时长(小时)
private Integer maxOnlineHours;
/// 最大活跃时长(小时), 0或null表示不限制
private Integer activeTimeoutHours;
/// 最大并发会话数
private Integer maxConcurrentSessions;
/// 并发会话策略

View File

@@ -17,6 +17,9 @@ public class PlatformSessionManagementConfigParam {
@Schema(description = "最大在线时长(小时)")
private Integer maxOnlineHours;
@Schema(description = "最大活跃时长小时0或留空表示不限制")
private Integer activeTimeoutHours;
@Schema(description = "最大并发会话数")
private Integer maxConcurrentSessions;

View File

@@ -20,6 +20,9 @@ public class PlatformSessionManagementConfigResult extends BaseResult {
@Schema(description = "最大在线时长(小时)")
private Integer maxOnlineHours;
@Schema(description = "最大活跃时长小时0或留空表示不限制")
private Integer activeTimeoutHours;
@Schema(description = "最大并发会话数")
private Integer maxConcurrentSessions;