feat: 增加多端登录可配置

This commit is contained in:
2026-08-03 15:27:47 +08:00
parent 3a77149087
commit 08fe3ba1f1
6 changed files with 57 additions and 14 deletions

View File

@@ -51,9 +51,10 @@ public final class AuthLoginSessionPolicy {
/**
* 判断 Web 登录是否需要替换既有 Web 会话。
*
* @return 始终为 true
* @param multiLogin 是否允许账号多端登录
* @return 禁止多端登录时返回 true
*/
public static boolean shouldReplaceExistingWebSession() {
return true;
public static boolean shouldReplaceExistingWebSession(boolean multiLogin) {
return !multiLogin;
}
}

View File

@@ -16,12 +16,13 @@ import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysApiKey;
import tech.easyflow.system.config.AccountSecurityProperties;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.entity.SysApiKey;
import tech.easyflow.system.entity.SysMenu;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.service.SysApiKeyService;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysApiKeyService;
import tech.easyflow.system.service.SysMenuService;
import tech.easyflow.system.service.SysRoleService;
import tech.easyflow.system.util.SysPasswordPolicy;
@@ -54,6 +55,8 @@ public class AuthServiceImpl implements AuthService, StpInterface {
private SysApiKeyService sysApiKeyService;
@Resource
private RedisLockExecutor redisLockExecutor;
@Resource
private AccountSecurityProperties accountSecurityProperties;
@Override
public LoginVO login(LoginDTO loginDTO) {
@@ -171,22 +174,23 @@ public class AuthServiceImpl implements AuthService, StpInterface {
}
/**
* 创建互斥的 Web 登录会话。
* 根据账号安全策略创建 Web 登录会话。
*
* @param record 登录账号
* @return 登录结果
*/
private LoginVO createWebLoginVO(SysAccount record) {
if (!AuthLoginSessionPolicy.shouldReplaceExistingWebSession(accountSecurityProperties.isMultiLogin())) {
return createLoginVO(record, AuthLoginSessionPolicy.webLoginModel());
}
return redisLockExecutor.executeWithLock(
WEB_LOGIN_LOCK_KEY_PREFIX + record.getId(),
WEB_LOGIN_LOCK_WAIT,
WEB_LOGIN_LOCK_LEASE,
() -> {
if (AuthLoginSessionPolicy.shouldReplaceExistingWebSession()) {
StpUtil.replaced(record.getId(), AuthLoginSessionPolicy.WEB_DEVICE);
// 首次升级后的新登录同时淘汰旧版本未标记设备类型的浏览器会话。
StpUtil.replaced(record.getId(), AuthLoginSessionPolicy.LEGACY_WEB_DEVICE);
}
StpUtil.replaced(record.getId(), AuthLoginSessionPolicy.WEB_DEVICE);
// 首次升级后的新登录同时淘汰旧版本未标记设备类型的浏览器会话。
StpUtil.replaced(record.getId(), AuthLoginSessionPolicy.LEGACY_WEB_DEVICE);
return createLoginVO(record, AuthLoginSessionPolicy.webLoginModel());
}
);

View File

@@ -10,7 +10,7 @@ import org.junit.Test;
public class AuthLoginSessionPolicyTest {
/**
* 验证 Web 登录使用独立设备类型并要求替换旧会话
* 验证 Web 登录设备类型和多端登录会话替换策略
*/
@Test
public void shouldBuildExclusiveWebLoginModel() {
@@ -18,7 +18,8 @@ public class AuthLoginSessionPolicyTest {
Assert.assertEquals("WEB", model.getDevice());
Assert.assertEquals("default-device", AuthLoginSessionPolicy.LEGACY_WEB_DEVICE);
Assert.assertTrue(AuthLoginSessionPolicy.shouldReplaceExistingWebSession());
Assert.assertTrue(AuthLoginSessionPolicy.shouldReplaceExistingWebSession(false));
Assert.assertFalse(AuthLoginSessionPolicy.shouldReplaceExistingWebSession(true));
}
/**

View File

@@ -17,6 +17,11 @@ public class AccountSecurityProperties implements InitializingBean {
*/
private String defaultResetPassword = "!QAZ2wsx";
/**
* 是否允许同一账号同时在多个客户端登录。
*/
private boolean multiLogin = false;
/**
* 获取默认重置密码。
*
@@ -35,6 +40,24 @@ public class AccountSecurityProperties implements InitializingBean {
this.defaultResetPassword = defaultResetPassword;
}
/**
* 判断是否允许同一账号同时在多个客户端登录。
*
* @return 允许多端登录时返回 true
*/
public boolean isMultiLogin() {
return multiLogin;
}
/**
* 设置是否允许同一账号同时在多个客户端登录。
*
* @param multiLogin 是否允许多端登录
*/
public void setMultiLogin(boolean multiLogin) {
this.multiLogin = multiLogin;
}
/**
* 应用启动时校验默认密码符合系统强密码策略。
*/

View File

@@ -19,6 +19,19 @@ public class AccountSecurityPropertiesTest {
properties.afterPropertiesSet();
Assert.assertEquals("!QAZ2wsx", properties.getDefaultResetPassword());
Assert.assertFalse(properties.isMultiLogin());
}
/**
* 验证账号多端登录配置可以开启。
*/
@Test
public void shouldEnableAccountMultiLogin() {
AccountSecurityProperties properties = new AccountSecurityProperties();
properties.setMultiLogin(true);
Assert.assertTrue(properties.isMultiLogin());
}
/**

View File

@@ -98,7 +98,8 @@ easyflow:
cron: "0 30 2 * * *"
security:
account:
default-reset-password: '${EASYFLOW_DEFAULT_RESET_PASSWORD:!QAZ2wsx}'
default-reset-password: "!QAZ2wsx"
multi-login: false
license:
location: classpath:easyflow.lic
chat: