feat: 完成分享、单会话与发布审批改造
- 增加工作流协作分享与知识库卡片分享入口,统一低版本浏览器复制反馈 - Web 新登录替换旧会话,并保持 API Key 会话隔离 - 发布审批增加必填说明并在审批详情展示 - 账号重置与导入改用可配置默认强密码
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package tech.easyflow.auth.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
|
||||
/**
|
||||
* 登录会话来源隔离策略。
|
||||
*/
|
||||
public final class AuthLoginSessionPolicy {
|
||||
|
||||
/**
|
||||
* 人类用户 Web 会话设备类型。
|
||||
*/
|
||||
public static final String WEB_DEVICE = "WEB";
|
||||
|
||||
/**
|
||||
* 升级前未显式指定设备类型的历史 Web 会话设备类型。
|
||||
*/
|
||||
public static final String LEGACY_WEB_DEVICE = "default-device";
|
||||
|
||||
/**
|
||||
* API Key 机器会话设备类型。
|
||||
*/
|
||||
public static final String API_KEY_DEVICE = "API_KEY";
|
||||
|
||||
private AuthLoginSessionPolicy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Web 登录模型。
|
||||
*
|
||||
* @return Web 登录模型
|
||||
*/
|
||||
public static SaLoginModel webLoginModel() {
|
||||
return new SaLoginModel().setDevice(WEB_DEVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 API Key 登录模型。
|
||||
*
|
||||
* @param timeoutSeconds 会话有效期秒数,可为空
|
||||
* @return API Key 登录模型
|
||||
*/
|
||||
public static SaLoginModel apiKeyLoginModel(Long timeoutSeconds) {
|
||||
SaLoginModel loginModel = new SaLoginModel().setDevice(API_KEY_DEVICE);
|
||||
if (timeoutSeconds != null) {
|
||||
loginModel.setTimeout(timeoutSeconds);
|
||||
}
|
||||
return loginModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Web 登录是否需要替换既有 Web 会话。
|
||||
*
|
||||
* @return 始终为 true
|
||||
*/
|
||||
public static boolean shouldReplaceExistingWebSession() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.auth.entity.LoginDTO;
|
||||
import tech.easyflow.auth.entity.LoginVO;
|
||||
import tech.easyflow.auth.service.AuthService;
|
||||
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||
import tech.easyflow.common.constant.Constants;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
@@ -26,6 +27,7 @@ import cn.hutool.crypto.digest.BCrypt;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -33,6 +35,10 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
|
||||
private static final String WEB_LOGIN_LOCK_KEY_PREFIX = "easyflow:lock:auth:web-login:";
|
||||
private static final Duration WEB_LOGIN_LOCK_WAIT = Duration.ofSeconds(3);
|
||||
private static final Duration WEB_LOGIN_LOCK_LEASE = Duration.ofSeconds(10);
|
||||
|
||||
@Resource
|
||||
private SysAccountService sysAccountService;
|
||||
@Resource
|
||||
@@ -41,6 +47,8 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
private SysMenuService sysMenuService;
|
||||
@Resource
|
||||
private SysApiKeyService sysApiKeyService;
|
||||
@Resource
|
||||
private RedisLockExecutor redisLockExecutor;
|
||||
|
||||
@Override
|
||||
public LoginVO login(LoginDTO loginDTO) {
|
||||
@@ -52,7 +60,7 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
if (!BCrypt.checkpw(pwd, pwdDb)) {
|
||||
throw new BusinessException("用户名/密码错误");
|
||||
}
|
||||
return createLoginVO(record);
|
||||
return createWebLoginVO(record);
|
||||
} finally {
|
||||
TenantManager.restoreTenantCondition();
|
||||
}
|
||||
@@ -63,7 +71,7 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
try {
|
||||
TenantManager.ignoreTenantCondition();
|
||||
SysAccount record = getAvailableAccount(account, "开发免登账号不存在");
|
||||
return createLoginVO(record);
|
||||
return createWebLoginVO(record);
|
||||
} finally {
|
||||
TenantManager.restoreTenantCondition();
|
||||
}
|
||||
@@ -89,7 +97,7 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
@Override
|
||||
public LoginVO loginByAccountId(BigInteger accountId, Long timeoutSeconds) {
|
||||
SysAccount record = getAvailableAccount(accountId, "账号不存在或不可用");
|
||||
return createLoginVO(record, timeoutSeconds);
|
||||
return createApiKeyLoginVO(record, timeoutSeconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,18 +115,48 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
return roles.stream().map(SysRole::getRoleKey).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private LoginVO createLoginVO(SysAccount record) {
|
||||
return createLoginVO(record, null);
|
||||
/**
|
||||
* 创建互斥的 Web 登录会话。
|
||||
*
|
||||
* @param record 登录账号
|
||||
* @return 登录结果
|
||||
*/
|
||||
private LoginVO createWebLoginVO(SysAccount record) {
|
||||
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);
|
||||
}
|
||||
return createLoginVO(record, AuthLoginSessionPolicy.webLoginModel());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private LoginVO createLoginVO(SysAccount record, Long timeoutSeconds) {
|
||||
if (timeoutSeconds != null) {
|
||||
SaLoginModel loginModel = new SaLoginModel();
|
||||
loginModel.setTimeout(timeoutSeconds);
|
||||
StpUtil.login(record.getId(), loginModel);
|
||||
} else {
|
||||
StpUtil.login(record.getId());
|
||||
}
|
||||
/**
|
||||
* 创建独立的 API Key 机器会话。
|
||||
*
|
||||
* @param record 登录账号
|
||||
* @param timeoutSeconds 会话有效期秒数
|
||||
* @return 登录结果
|
||||
*/
|
||||
private LoginVO createApiKeyLoginVO(SysAccount record, Long timeoutSeconds) {
|
||||
return createLoginVO(record, AuthLoginSessionPolicy.apiKeyLoginModel(timeoutSeconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定来源的登录会话。
|
||||
*
|
||||
* @param record 登录账号
|
||||
* @param loginModel 登录参数
|
||||
* @return 登录结果
|
||||
*/
|
||||
private LoginVO createLoginVO(SysAccount record, SaLoginModel loginModel) {
|
||||
StpUtil.login(record.getId(), loginModel);
|
||||
LoginAccount loginAccount = new LoginAccount();
|
||||
BeanUtil.copyProperties(record, loginAccount);
|
||||
StpUtil.getSession().set(Constants.LOGIN_USER_KEY, loginAccount);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package tech.easyflow.auth.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* {@link AuthLoginSessionPolicy} 测试。
|
||||
*/
|
||||
public class AuthLoginSessionPolicyTest {
|
||||
|
||||
/**
|
||||
* 验证 Web 登录使用独立设备类型并要求替换旧会话。
|
||||
*/
|
||||
@Test
|
||||
public void shouldBuildExclusiveWebLoginModel() {
|
||||
SaLoginModel model = AuthLoginSessionPolicy.webLoginModel();
|
||||
|
||||
Assert.assertEquals("WEB", model.getDevice());
|
||||
Assert.assertEquals("default-device", AuthLoginSessionPolicy.LEGACY_WEB_DEVICE);
|
||||
Assert.assertTrue(AuthLoginSessionPolicy.shouldReplaceExistingWebSession());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 API Key 会话使用独立设备类型和指定有效期。
|
||||
*/
|
||||
@Test
|
||||
public void shouldBuildIndependentApiKeyLoginModel() {
|
||||
SaLoginModel model = AuthLoginSessionPolicy.apiKeyLoginModel(120L);
|
||||
|
||||
Assert.assertEquals("API_KEY", model.getDevice());
|
||||
Assert.assertEquals(Long.valueOf(120L), model.getTimeout());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user