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 5142ac9..2d385c6 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,6 +174,15 @@ public class SecureUtil { * @return boolean */ public static boolean isAdministrator() { + return StringUtil.containsAny(getUserRole(), RoleConstant.ADMINISTRATOR); + } + + /** + * 是否为管理员 + * + * @return boolean + */ + public static boolean isAdmin() { return StringUtil.containsAny(getUserRole(), RoleConstant.ADMIN); } 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 new file mode 100644 index 0000000..7e8b0df --- /dev/null +++ b/blade-starter-tenant/src/main/java/org/springblade/core/tenant/TenantGuard.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). + *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.tenant; + +import com.baomidou.mybatisplus.extension.service.IService; +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 java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 租户归属守卫 + *
+ * 统一拦截「目标实体是否属于当前会话租户」的越权写操作。平台超管(administrator)默认放行。
+ * 通过反射访问实体的 getId / getTenantId / setTenantId 方法,调用方无需传入 lambda。
+ *
+ * @author Chill
+ */
+public class TenantGuard {
+
+ private static final String METHOD_GET_ID = "getId";
+ private static final String METHOD_GET_TENANT_ID = "getTenantId";
+ private static final String METHOD_SET_TENANT_ID = "setTenantId";
+
+ private static final ConcurrentHashMap
+ * 集中管理实体中文标签,避免调用处散落硬编码字符串。新增受保护实体时,
+ * 在此枚举追加常量即可,调用方使用 static import 让代码更紧凑。
+ */
+ public enum EntityType {
+
+ ROLE("角色"),
+ USER("用户"),
+ DEPT("部门"),
+ DEPT_PARENT("上级部门"),
+ POST("岗位"),
+ ;
+
+ private final String label;
+
+ EntityType(String label) {
+ this.label = label;
+ }
+
+ public String label() {
+ return label;
+ }
+ }
+
+ /**
+ * 校验单个 id 归属当前会话租户
+ *
+ * 适用于 update/detail by id 等场景。校验通过后返回查询出的实体,
+ * 调用方可基于返回值进行后续业务(如把 existEntity.tenantId 回写到入参以防篡改)。
+ *
+ * @param service MyBatis-Plus IService 实例
+ * @param id 目标主键
+ * @param entityType 实体类型枚举
+ * @param
+ * 新增(id 为空):超管放行,非超管强制写入当前会话 tenantId,避免前端注入。
+ * 适用于 batch remove/grant/reset/unlock 等场景。任一 id 不存在或归属其他租户,整体抛异常。
+ * 超管放行。返回值供调用方复用,避免二次查询。
+ *
+ * @param service MyBatis-Plus IService 实例
+ * @param ids 目标主键集合
+ * @param entityType 实体类型枚举
+ * @param
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.tenant.error;
+
+import jakarta.servlet.Servlet;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tenant.exception.TenantException;
+import org.springblade.core.tool.api.R;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.DispatcherServlet;
+
+/**
+ * 租户异常处理器
+ *
+ * 仅当 tenant starter 上线时随 {@code TenantConfiguration} 一起激活,
+ * 把 {@link TenantException} 翻译为 HTTP 403。优先级高于全局兜底翻译器,
+ * 避免被 {@code Throwable} handler 吞掉。
+ *
+ * @author Chill
+ */
+@Slf4j
+@AutoConfiguration
+@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+@RestControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class BladeTenantExceptionTranslator {
+
+ @ExceptionHandler(TenantException.class)
+ @ResponseStatus(HttpStatus.FORBIDDEN)
+ public R handleError(TenantException e) {
+ log.error("租户异常", e);
+ return R.fail(e.getResultCode(), e.getMessage());
+ }
+
+}
diff --git a/blade-starter-tenant/src/main/java/org/springblade/core/tenant/exception/TenantException.java b/blade-starter-tenant/src/main/java/org/springblade/core/tenant/exception/TenantException.java
new file mode 100644
index 0000000..1800a55
--- /dev/null
+++ b/blade-starter-tenant/src/main/java/org/springblade/core/tenant/exception/TenantException.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.tenant.exception;
+
+import lombok.Getter;
+import org.springblade.core.tool.api.IResultCode;
+import org.springblade.core.tool.api.ResultCode;
+
+/**
+ * 租户业务异常
+ *
+ * @author Chill
+ */
+public class TenantException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ @Getter
+ private final IResultCode resultCode;
+
+ public TenantException(String message) {
+ super(message);
+ this.resultCode = ResultCode.FAILURE;
+ }
+
+ public TenantException(IResultCode resultCode) {
+ super(resultCode.getMessage());
+ this.resultCode = resultCode;
+ }
+
+ public TenantException(IResultCode resultCode, Throwable cause) {
+ super(cause);
+ this.resultCode = resultCode;
+ }
+
+ /**
+ * 提高性能
+ *
+ * @return Throwable
+ */
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+
+ public Throwable doFillInStackTrace() {
+ return super.fillInStackTrace();
+ }
+
+}
+ * 修改(id 不空):调用 {@link #verify} 校验归属,并把已存在实体的 tenantId 回写到入参,防止 update 篡改。
+ *
+ * @param service MyBatis-Plus IService 实例
+ * @param entity 入参实体
+ * @param entityType 实体类型
+ * @param