From 08fe3ba1f14ea9de947bf56360b8e98ecec99098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Mon, 3 Aug 2026 15:27:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=A4=9A=E7=AB=AF?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=8F=AF=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/AuthLoginSessionPolicy.java | 7 +++--- .../auth/service/impl/AuthServiceImpl.java | 20 +++++++++------- .../impl/AuthLoginSessionPolicyTest.java | 5 ++-- .../config/AccountSecurityProperties.java | 23 +++++++++++++++++++ .../config/AccountSecurityPropertiesTest.java | 13 +++++++++++ .../src/main/resources/application.yml | 3 ++- 6 files changed, 57 insertions(+), 14 deletions(-) diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthLoginSessionPolicy.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthLoginSessionPolicy.java index a202d90c..b1026425 100644 --- a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthLoginSessionPolicy.java +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthLoginSessionPolicy.java @@ -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; } } diff --git a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthServiceImpl.java b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthServiceImpl.java index 6c09fe60..ba10a739 100644 --- a/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthServiceImpl.java +++ b/easyflow-modules/easyflow-module-auth/src/main/java/tech/easyflow/auth/service/impl/AuthServiceImpl.java @@ -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()); } ); diff --git a/easyflow-modules/easyflow-module-auth/src/test/java/tech/easyflow/auth/service/impl/AuthLoginSessionPolicyTest.java b/easyflow-modules/easyflow-module-auth/src/test/java/tech/easyflow/auth/service/impl/AuthLoginSessionPolicyTest.java index 094ae5a1..5bfdec4e 100644 --- a/easyflow-modules/easyflow-module-auth/src/test/java/tech/easyflow/auth/service/impl/AuthLoginSessionPolicyTest.java +++ b/easyflow-modules/easyflow-module-auth/src/test/java/tech/easyflow/auth/service/impl/AuthLoginSessionPolicyTest.java @@ -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)); } /** diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/AccountSecurityProperties.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/AccountSecurityProperties.java index b8dd347d..efa5388c 100644 --- a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/AccountSecurityProperties.java +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/config/AccountSecurityProperties.java @@ -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; + } + /** * 应用启动时校验默认密码符合系统强密码策略。 */ diff --git a/easyflow-modules/easyflow-module-system/src/test/java/tech/easyflow/system/config/AccountSecurityPropertiesTest.java b/easyflow-modules/easyflow-module-system/src/test/java/tech/easyflow/system/config/AccountSecurityPropertiesTest.java index ff2fd25e..d8a7c7e5 100644 --- a/easyflow-modules/easyflow-module-system/src/test/java/tech/easyflow/system/config/AccountSecurityPropertiesTest.java +++ b/easyflow-modules/easyflow-module-system/src/test/java/tech/easyflow/system/config/AccountSecurityPropertiesTest.java @@ -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()); } /** diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/application.yml b/easyflow-starter/easyflow-starter-all/src/main/resources/application.yml index 87a95c89..a0939ded 100644 --- a/easyflow-starter/easyflow-starter-all/src/main/resources/application.yml +++ b/easyflow-starter/easyflow-starter-all/src/main/resources/application.yml @@ -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: