🔒 角色判定改为精确匹配,租户守卫增强超管新增归属绑定与批量ID去重

This commit is contained in:
smallchill 2026-08-11 19:30:40 +08:00
parent 3d93b3d587
commit 9bceec2067
2 changed files with 35 additions and 8 deletions

View File

@ -174,7 +174,7 @@ public class SecureUtil {
* @return boolean
*/
public static boolean isAdministrator() {
return StringUtil.containsAny(getUserRole(), RoleConstant.ADMINISTRATOR);
return hasRole(RoleConstant.ADMINISTRATOR);
}
/**
@ -183,7 +183,17 @@ public class SecureUtil {
* @return boolean
*/
public static boolean isAdmin() {
return StringUtil.containsAny(getUserRole(), RoleConstant.ADMIN);
return hasRole(RoleConstant.ADMIN);
}
/**
* 判定当前会话是否持有指定角色
*
* @param role 角色别名
* @return boolean
*/
private static boolean hasRole(String role) {
return CollectionUtil.contains(Func.toStrArray(getUserRole()), role);
}
/**

View File

@ -20,6 +20,7 @@ import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tenant.exception.TenantException;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.ReflectUtil;
import org.springblade.core.tool.utils.StringUtil;
import java.lang.reflect.Method;
import java.util.Collections;
@ -100,7 +101,8 @@ public class TenantGuard {
/**
* 提交时新增 / 修改的租户绑定守卫
* <p>
* 新增id 为空超管放行非超管强制写入当前会话 tenantId避免前端注入<br/>
* 新增id 为空非超管强制写入当前会话 tenantId 避免前端注入超管保留指定归属租户的能力
* 入参未指定时兜底为会话 tenantId<br/>
* 修改id 不空调用 {@link #verify} 校验归属并把已存在实体的 tenantId 回写到入参防止 update 篡改
*
* @param service MyBatis-Plus IService 实例
@ -112,13 +114,13 @@ public class TenantGuard {
public static <T> void bindTenant(IService<T> service, T entity, EntityType entityType) {
Long id = idOf(entity);
if (id == null) {
if (!SecureUtil.isAdministrator()) {
bindTenantId(entity, SecureUtil.getTenantId());
}
bindTenantOnCreate(entity);
return;
}
T existEntity = verify(service, id, entityType);
if (existEntity == null) {
// 超管提交了库中不存在的 idsaveOrUpdate 将退化为新增按新增路径绑定归属
bindTenantOnCreate(entity);
return;
}
// 非超管路径下 verify 已确保 existEntity.tenantId 等于会话 tenantId直接复用避免重复反射
@ -126,6 +128,19 @@ public class TenantGuard {
bindTenantId(entity, tenantId);
}
/**
* 新增路径的租户归属绑定
* <p>
* 非超管一律以会话 tenantId 覆盖入参杜绝伪造归属超管保留跨租户建数据的能力仅在入参未携带
* tenantId 时兜底为会话 tenantId 业务表单未必向超管暴露租户选择项不兜底则记录会以空归属落库
* 脱离所有租户的数据范围且无法被租户条件检索
*/
private static void bindTenantOnCreate(Object entity) {
if (!SecureUtil.isAdministrator() || StringUtil.isBlank(tenantIdOf(entity))) {
bindTenantId(entity, SecureUtil.getTenantId());
}
}
/**
* 校验批量 ids 全部归属当前会话租户并返回查询出的实体列表
* <p>
@ -143,12 +158,14 @@ public class TenantGuard {
if (CollectionUtil.isEmpty(ids)) {
return Collections.emptyList();
}
List<T> list = service.listByIds(ids);
// 去重入参主键避免重复 id 导致查询行数不等于入参数量而误判为越权
List<Long> distinctIds = ids.stream().distinct().toList();
List<T> list = service.listByIds(distinctIds);
if (SecureUtil.isAdministrator()) {
return list;
}
String currentTenantId = SecureUtil.getTenantId();
if (list.size() != ids.size() || list.stream().anyMatch(entity -> !currentTenantId.equals(tenantIdOf(entity)))) {
if (list.size() != distinctIds.size() || list.stream().anyMatch(entity -> !currentTenantId.equals(tenantIdOf(entity)))) {
throw new TenantException("无权操作非本租户的" + entityType.label());
}
return list;