feat: 完成分享、单会话与发布审批改造
- 增加工作流协作分享与知识库卡片分享入口,统一低版本浏览器复制反馈 - Web 新登录替换旧会话,并保持 API Key 会话隔离 - 发布审批增加必填说明并在审批详情展示 - 账号重置与导入改用可配置默认强密码
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package tech.easyflow.system.config;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.system.util.SysPasswordPolicy;
|
||||
|
||||
/**
|
||||
* 账号安全配置。
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "easyflow.security.account")
|
||||
public class AccountSecurityProperties implements InitializingBean {
|
||||
|
||||
/**
|
||||
* 账号重置和导入时使用的默认强密码。
|
||||
*/
|
||||
private String defaultResetPassword = "!QAZ2wsx";
|
||||
|
||||
/**
|
||||
* 获取默认重置密码。
|
||||
*
|
||||
* @return 默认重置密码
|
||||
*/
|
||||
public String getDefaultResetPassword() {
|
||||
return defaultResetPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认重置密码。
|
||||
*
|
||||
* @param defaultResetPassword 默认重置密码
|
||||
*/
|
||||
public void setDefaultResetPassword(String defaultResetPassword) {
|
||||
this.defaultResetPassword = defaultResetPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用启动时校验默认密码符合系统强密码策略。
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
SysPasswordPolicy.validateStrongPassword(
|
||||
defaultResetPassword == null ? null : defaultResetPassword.trim()
|
||||
);
|
||||
defaultResetPassword = defaultResetPassword.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package tech.easyflow.system.permission.resource;
|
||||
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
|
||||
/**
|
||||
* 为特定业务场景补充资源访问授权的扩展点。
|
||||
*/
|
||||
public interface ResourceAccessGrantProvider {
|
||||
|
||||
/**
|
||||
* 判断当前业务上下文是否补充授予资源动作权限。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param resourceType 资源类型
|
||||
* @param resource 资源对象
|
||||
* @param action 资源动作
|
||||
* @return 授予权限时返回 {@code true}
|
||||
*/
|
||||
boolean grants(
|
||||
LoginAccount loginAccount,
|
||||
CategoryResourceType resourceType,
|
||||
VisibilityResource resource,
|
||||
ResourceAction action
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package tech.easyflow.system.service.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
@@ -7,6 +8,7 @@ import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.enums.VisibilityScope;
|
||||
import tech.easyflow.system.permission.resource.ResourceAccessGrantProvider;
|
||||
import tech.easyflow.system.permission.resource.VisibilityResource;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
@@ -14,7 +16,12 @@ import tech.easyflow.system.service.SysDeptService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于租户、创建者、分类授权和可见范围的统一资源权限实现。
|
||||
*/
|
||||
@Service
|
||||
public class ResourceAccessServiceImpl implements ResourceAccessService {
|
||||
|
||||
@@ -24,11 +31,20 @@ public class ResourceAccessServiceImpl implements ResourceAccessService {
|
||||
@Resource
|
||||
private SysDeptService sysDeptService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<ResourceAccessGrantProvider> grantProviders = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean canAccess(CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action) {
|
||||
return canAccess(SaTokenUtil.getLoginAccount(), resourceType, resource, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean canAccess(LoginAccount loginAccount, CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action) {
|
||||
if (resource == null) {
|
||||
@@ -38,6 +54,10 @@ public class ResourceAccessServiceImpl implements ResourceAccessService {
|
||||
return false;
|
||||
}
|
||||
BigInteger accountId = loginAccount.getId();
|
||||
// 分享访问需要先完成密钥校验与审计,即使当前账号同时也是资源创建者或超管。
|
||||
if (hasExtendedGrant(loginAccount, resourceType, resource, action)) {
|
||||
return true;
|
||||
}
|
||||
if (categoryPermissionService.isSuperAdmin(loginAccount)) {
|
||||
return true;
|
||||
}
|
||||
@@ -60,10 +80,36 @@ public class ResourceAccessServiceImpl implements ResourceAccessService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void assertAccess(CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action, String message) {
|
||||
if (!canAccess(resourceType, resource, action)) {
|
||||
throw new BusinessException(message == null ? "无权限访问该资源" : message);
|
||||
throw new BusinessException(403, 403, message == null ? "无权限访问该资源" : message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断业务扩展授权是否允许当前动作。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param resourceType 资源类型
|
||||
* @param resource 资源对象
|
||||
* @param action 资源动作
|
||||
* @return 任一扩展授权允许时返回 {@code true}
|
||||
*/
|
||||
private boolean hasExtendedGrant(
|
||||
LoginAccount loginAccount,
|
||||
CategoryResourceType resourceType,
|
||||
VisibilityResource resource,
|
||||
ResourceAction action
|
||||
) {
|
||||
for (ResourceAccessGrantProvider provider : grantProviders) {
|
||||
if (provider.grants(loginAccount, resourceType, resource, action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.util.StringUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.config.AccountSecurityProperties;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.entity.SysAccountPosition;
|
||||
import tech.easyflow.system.entity.SysAccountRole;
|
||||
@@ -71,7 +72,6 @@ public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAcco
|
||||
private static final String ACCOUNT_RELATION_LOCK_KEY_PREFIX = "easyflow:lock:sys:account:relation:";
|
||||
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||
private static final String DEFAULT_RESET_PASSWORD = "123456";
|
||||
private static final long MAX_IMPORT_FILE_SIZE_BYTES = 10L * 1024 * 1024;
|
||||
private static final int MAX_IMPORT_ROWS = 5000;
|
||||
private static final String IMPORT_HEAD_DEPT_NAME = "部门名称*";
|
||||
@@ -110,6 +110,8 @@ public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAcco
|
||||
private RedisLockExecutor redisLockExecutor;
|
||||
@Resource
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Resource
|
||||
private AccountSecurityProperties accountSecurityProperties;
|
||||
|
||||
/**
|
||||
* 批量解析账号展示名称。
|
||||
@@ -238,7 +240,7 @@ public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAcco
|
||||
validateResetPasswordAllowed(record);
|
||||
SysAccount update = new SysAccount();
|
||||
update.setId(accountId);
|
||||
update.setPassword(BCrypt.hashpw(DEFAULT_RESET_PASSWORD));
|
||||
update.setPassword(BCrypt.hashpw(accountSecurityProperties.getDefaultResetPassword()));
|
||||
update.setPasswordResetRequired(true);
|
||||
update.setModified(new Date());
|
||||
update.setModifiedBy(operatorId);
|
||||
@@ -421,7 +423,7 @@ public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAcco
|
||||
entity.setDeptId(dept.getId());
|
||||
entity.setTenantId(loginAccount.getTenantId());
|
||||
entity.setLoginName(loginName);
|
||||
entity.setPassword(BCrypt.hashpw(DEFAULT_RESET_PASSWORD));
|
||||
entity.setPassword(BCrypt.hashpw(accountSecurityProperties.getDefaultResetPassword()));
|
||||
entity.setPasswordResetRequired(true);
|
||||
entity.setAccountType(EnumAccountType.NORMAL.getCode());
|
||||
entity.setNickname(nickname);
|
||||
@@ -686,7 +688,11 @@ public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAcco
|
||||
rows.add(List.of("可选字段", "手机号、邮箱、状态、角色名称、岗位名称、备注"));
|
||||
rows.add(List.of("状态可选值", "可留空,或填写 1/0/已启用/启用/未启用/停用/禁用"));
|
||||
rows.add(List.of("多值分隔", "角色名称、岗位名称支持使用英文逗号,或中文逗号,分隔多个名称"));
|
||||
rows.add(List.of("导入后初始密码", "导入成功的账号默认密码为 123456,首次登录需要修改密码"));
|
||||
rows.add(List.of(
|
||||
"导入后初始密码",
|
||||
"导入成功的账号默认密码为 " + accountSecurityProperties.getDefaultResetPassword()
|
||||
+ ",首次登录需要修改密码"
|
||||
));
|
||||
rows.add(List.of("示例行", "市场部 | zhangsan | 张三 | 13800138000 | zhangsan@example.com | 已启用 | 普通员工,审批专员 | 产品经理 | 示例导入"));
|
||||
return rows;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package tech.easyflow.system.config;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
/**
|
||||
* {@link AccountSecurityProperties} 测试。
|
||||
*/
|
||||
public class AccountSecurityPropertiesTest {
|
||||
|
||||
/**
|
||||
* 验证默认重置密码符合强密码策略。
|
||||
*/
|
||||
@Test
|
||||
public void shouldAcceptDefaultStrongResetPassword() {
|
||||
AccountSecurityProperties properties = new AccountSecurityProperties();
|
||||
|
||||
properties.afterPropertiesSet();
|
||||
|
||||
Assert.assertEquals("!QAZ2wsx", properties.getDefaultResetPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证弱默认密码会阻止应用启动。
|
||||
*/
|
||||
@Test(expected = BusinessException.class)
|
||||
public void shouldRejectWeakResetPassword() {
|
||||
AccountSecurityProperties properties = new AccountSecurityProperties();
|
||||
properties.setDefaultResetPassword("123456");
|
||||
|
||||
properties.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证空默认密码会阻止应用启动。
|
||||
*/
|
||||
@Test(expected = BusinessException.class)
|
||||
public void shouldRejectBlankResetPassword() {
|
||||
AccountSecurityProperties properties = new AccountSecurityProperties();
|
||||
properties.setDefaultResetPassword(" ");
|
||||
|
||||
properties.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user