发布 v1.10 #5

Merged
czm merged 147 commits from develop into main 2026-08-20 11:36:27 +08:00
6 changed files with 57 additions and 14 deletions
Showing only changes of commit 08fe3ba1f1 - Show all commits

View File

@@ -51,9 +51,10 @@ public final class AuthLoginSessionPolicy {
/** /**
* 判断 Web 登录是否需要替换既有 Web 会话。 * 判断 Web 登录是否需要替换既有 Web 会话。
* *
* @return 始终为 true * @param multiLogin 是否允许账号多端登录
* @return 禁止多端登录时返回 true
*/ */
public static boolean shouldReplaceExistingWebSession() { public static boolean shouldReplaceExistingWebSession(boolean multiLogin) {
return true; 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.constant.enums.EnumDataStatus;
import tech.easyflow.common.entity.LoginAccount; import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException; 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.SysAccount;
import tech.easyflow.system.entity.SysApiKey;
import tech.easyflow.system.entity.SysMenu; import tech.easyflow.system.entity.SysMenu;
import tech.easyflow.system.entity.SysRole; import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.service.SysApiKeyService;
import tech.easyflow.system.service.SysAccountService; import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysApiKeyService;
import tech.easyflow.system.service.SysMenuService; import tech.easyflow.system.service.SysMenuService;
import tech.easyflow.system.service.SysRoleService; import tech.easyflow.system.service.SysRoleService;
import tech.easyflow.system.util.SysPasswordPolicy; import tech.easyflow.system.util.SysPasswordPolicy;
@@ -54,6 +55,8 @@ public class AuthServiceImpl implements AuthService, StpInterface {
private SysApiKeyService sysApiKeyService; private SysApiKeyService sysApiKeyService;
@Resource @Resource
private RedisLockExecutor redisLockExecutor; private RedisLockExecutor redisLockExecutor;
@Resource
private AccountSecurityProperties accountSecurityProperties;
@Override @Override
public LoginVO login(LoginDTO loginDTO) { public LoginVO login(LoginDTO loginDTO) {
@@ -171,22 +174,23 @@ public class AuthServiceImpl implements AuthService, StpInterface {
} }
/** /**
* 创建互斥的 Web 登录会话。 * 根据账号安全策略创建 Web 登录会话。
* *
* @param record 登录账号 * @param record 登录账号
* @return 登录结果 * @return 登录结果
*/ */
private LoginVO createWebLoginVO(SysAccount record) { private LoginVO createWebLoginVO(SysAccount record) {
if (!AuthLoginSessionPolicy.shouldReplaceExistingWebSession(accountSecurityProperties.isMultiLogin())) {
return createLoginVO(record, AuthLoginSessionPolicy.webLoginModel());
}
return redisLockExecutor.executeWithLock( return redisLockExecutor.executeWithLock(
WEB_LOGIN_LOCK_KEY_PREFIX + record.getId(), WEB_LOGIN_LOCK_KEY_PREFIX + record.getId(),
WEB_LOGIN_LOCK_WAIT, WEB_LOGIN_LOCK_WAIT,
WEB_LOGIN_LOCK_LEASE, WEB_LOGIN_LOCK_LEASE,
() -> { () -> {
if (AuthLoginSessionPolicy.shouldReplaceExistingWebSession()) { StpUtil.replaced(record.getId(), AuthLoginSessionPolicy.WEB_DEVICE);
StpUtil.replaced(record.getId(), AuthLoginSessionPolicy.WEB_DEVICE); // 首次升级后的新登录同时淘汰旧版本未标记设备类型的浏览器会话。
// 首次升级后的新登录同时淘汰旧版本未标记设备类型的浏览器会话。 StpUtil.replaced(record.getId(), AuthLoginSessionPolicy.LEGACY_WEB_DEVICE);
StpUtil.replaced(record.getId(), AuthLoginSessionPolicy.LEGACY_WEB_DEVICE);
}
return createLoginVO(record, AuthLoginSessionPolicy.webLoginModel()); return createLoginVO(record, AuthLoginSessionPolicy.webLoginModel());
} }
); );

View File

@@ -10,7 +10,7 @@ import org.junit.Test;
public class AuthLoginSessionPolicyTest { public class AuthLoginSessionPolicyTest {
/** /**
* 验证 Web 登录使用独立设备类型并要求替换旧会话 * 验证 Web 登录设备类型和多端登录会话替换策略
*/ */
@Test @Test
public void shouldBuildExclusiveWebLoginModel() { public void shouldBuildExclusiveWebLoginModel() {
@@ -18,7 +18,8 @@ public class AuthLoginSessionPolicyTest {
Assert.assertEquals("WEB", model.getDevice()); Assert.assertEquals("WEB", model.getDevice());
Assert.assertEquals("default-device", AuthLoginSessionPolicy.LEGACY_WEB_DEVICE); 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 String defaultResetPassword = "!QAZ2wsx";
/**
* 是否允许同一账号同时在多个客户端登录。
*/
private boolean multiLogin = false;
/** /**
* 获取默认重置密码。 * 获取默认重置密码。
* *
@@ -35,6 +40,24 @@ public class AccountSecurityProperties implements InitializingBean {
this.defaultResetPassword = defaultResetPassword; 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(); properties.afterPropertiesSet();
Assert.assertEquals("!QAZ2wsx", properties.getDefaultResetPassword()); 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 * * *" cron: "0 30 2 * * *"
security: security:
account: account:
default-reset-password: '${EASYFLOW_DEFAULT_RESET_PASSWORD:!QAZ2wsx}' default-reset-password: "!QAZ2wsx"
multi-login: false
license: license:
location: classpath:easyflow.lic location: classpath:easyflow.lic
chat: chat: