From 9bceec2067392f6acf3ccb6662b2ab7eecdb46b6 Mon Sep 17 00:00:00 2001 From: smallchill Date: Tue, 11 Aug 2026 19:30:40 +0800 Subject: [PATCH] =?UTF-8?q?:lock:=20=E8=A7=92=E8=89=B2=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=B2=BE=E7=A1=AE=E5=8C=B9=E9=85=8D=EF=BC=8C?= =?UTF-8?q?=E7=A7=9F=E6=88=B7=E5=AE=88=E5=8D=AB=E5=A2=9E=E5=BC=BA=E8=B6=85?= =?UTF-8?q?=E7=AE=A1=E6=96=B0=E5=A2=9E=E5=BD=92=E5=B1=9E=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E4=B8=8E=E6=89=B9=E9=87=8FID=E5=8E=BB=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/secure/utils/SecureUtil.java | 14 +++++++-- .../springblade/core/tenant/TenantGuard.java | 29 +++++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java b/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java index 2d385c6..aceb71f 100644 --- a/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java +++ b/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java @@ -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); } /** diff --git a/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantGuard.java b/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantGuard.java index 1ec952b..c85ff08 100644 --- a/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantGuard.java +++ b/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantGuard.java @@ -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 { /** * 提交时(新增 / 修改)的租户绑定守卫 *

- * 新增(id 为空):超管放行,非超管强制写入当前会话 tenantId,避免前端注入。
+ * 新增(id 为空):非超管强制写入当前会话 tenantId 避免前端注入;超管保留指定归属租户的能力, + * 入参未指定时兜底为会话 tenantId。
* 修改(id 不空):调用 {@link #verify} 校验归属,并把已存在实体的 tenantId 回写到入参,防止 update 篡改。 * * @param service MyBatis-Plus IService 实例 @@ -112,13 +114,13 @@ public class TenantGuard { public static void bindTenant(IService 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) { + // 超管提交了库中不存在的 id,saveOrUpdate 将退化为新增,按新增路径绑定归属 + bindTenantOnCreate(entity); return; } // 非超管路径下 verify 已确保 existEntity.tenantId 等于会话 tenantId,直接复用避免重复反射 @@ -126,6 +128,19 @@ public class TenantGuard { bindTenantId(entity, tenantId); } + /** + * 新增路径的租户归属绑定 + *

+ * 非超管一律以会话 tenantId 覆盖入参,杜绝伪造归属;超管保留跨租户建数据的能力,仅在入参未携带 + * tenantId 时兜底为会话 tenantId —— 业务表单未必向超管暴露租户选择项,不兜底则记录会以空归属落库, + * 脱离所有租户的数据范围且无法被租户条件检索。 + */ + private static void bindTenantOnCreate(Object entity) { + if (!SecureUtil.isAdministrator() || StringUtil.isBlank(tenantIdOf(entity))) { + bindTenantId(entity, SecureUtil.getTenantId()); + } + } + /** * 校验批量 ids 全部归属当前会话租户,并返回查询出的实体列表 *

@@ -143,12 +158,14 @@ public class TenantGuard { if (CollectionUtil.isEmpty(ids)) { return Collections.emptyList(); } - List list = service.listByIds(ids); + // 去重入参主键,避免重复 id 导致查询行数不等于入参数量而误判为越权 + List distinctIds = ids.stream().distinct().toList(); + List 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;