diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.http b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.http
index c98984b69..f8e32a5b8 100644
--- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.http
+++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.http
@@ -18,7 +18,7 @@ tenant-id: 1
### 请求 /list-menus 接口 => 成功
GET {{baseUrl}}/list-menus
Authorization: Bearer {{token}}
-#Authorization: Bearer 0d161f69c9ac4c7f836e1b850715a7b0
+#Authorization: Bearer a6aa7714a2e44c95aaa8a2c5adc2a67a
tenant-id: 1
### 请求 /druid/xxx 接口 => 失败 TODO 临时测试
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java
index 9a8acca80..2c4dfc4c6 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java
@@ -17,13 +17,15 @@ public interface WebFilterOrderEnum {
// OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
- int TENANT_CONTEXT_FILTER = - 100; // 需要保证在 ApiAccessLogFilter 前面
+ int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
- int API_ACCESS_LOG_FILTER = -90; // 需要保证在 RequestBodyCacheFilter 后面
+ int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
- int XSS_FILTER = -80; // 需要保证在 RequestBodyCacheFilter 后面
+ int XSS_FILTER = -102; // 需要保证在 RequestBodyCacheFilter 后面
- // Spring Security Filter 默认为 -100,可见 SecurityProperties 配置属性类
+ // Spring Security Filter 默认为 -100,可见 org.springframework.boot.autoconfigure.security.SecurityProperties 配置属性类
+
+ int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后
int DEMO_FILTER = Integer.MAX_VALUE;
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/pom.xml b/yudao-framework/yudao-spring-boot-starter-tenant/pom.xml
index bc4302809..e7d81f3d6 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/pom.xml
@@ -27,6 +27,11 @@
spring-boot-starter-web
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-security
+
+
cn.iocoder.boot
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java
index 5b97b22f6..33390f631 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java
@@ -14,6 +14,15 @@ import java.util.Set;
@Data
public class TenantProperties {
+// /**
+// * 租户是否开启
+// */
+// private static final Boolean ENABLE_DEFAULT = true;
+//
+// /**
+// * 是否开启
+// */
+// private Boolean enable = ENABLE_DEFAULT;
/**
* 需要多租户的表
*
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantJobAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantJobAutoConfiguration.java
index d9d2f3823..89b58f86e 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantJobAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantJobAutoConfiguration.java
@@ -8,12 +8,14 @@ import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
/**
* 多租户针对 Job 的自动配置
*
* @author 芋道源码
*/
+@Configuration
public class YudaoTenantJobAutoConfiguration {
@Bean
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantSecurityAutoConfiguration.java
new file mode 100644
index 000000000..b5dbd0000
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantSecurityAutoConfiguration.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.tenant.config;
+
+import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
+import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 多租户针对 Web 的自动配置
+ *
+ * @author 芋道源码
+ */
+@Configuration
+public class YudaoTenantSecurityAutoConfiguration {
+
+ @Bean
+ public FilterRegistrationBean tenantSecurityWebFilter() {
+ FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(new TenantSecurityWebFilter());
+ registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);
+ return registrationBean;
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantWebAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantWebAutoConfiguration.java
index faacbc486..c2830a79a 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantWebAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantWebAutoConfiguration.java
@@ -4,12 +4,14 @@ import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
/**
* 多租户针对 Web 的自动配置
*
* @author 芋道源码
*/
+@Configuration
public class YudaoTenantWebAutoConfiguration {
@Bean
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
index 92dbff002..cf5a94141 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
@@ -11,10 +11,28 @@ public class TenantContextHolder {
private static final ThreadLocal TENANT_ID = new TransmittableThreadLocal<>();
+ /**
+ * 获得租户编号。
+ *
+ * @return 租户编号
+ */
public static Long getTenantId() {
return TENANT_ID.get();
}
+ /**
+ * 获得租户编号。如果不存在,则抛出 NullPointerException 异常
+ *
+ * @return 租户编号
+ */
+ public static Long getRequiredTenantId() {
+ Long tenantId = getTenantId();
+ if (tenantId == null) {
+ throw new NullPointerException("TenantContextHolder 不存在租户编号");
+ }
+ return tenantId;
+ }
+
public static void setTenantId(Long tenantId) {
TENANT_ID.set(tenantId);
}
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java
index dfcd8a4a4..055a76a1e 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java
@@ -20,8 +20,7 @@ public class TenantDatabaseInterceptor implements TenantLineHandler {
@Override
public Expression getTenantId() {
- // TODO 芋艿:暂时不考虑获取不到的情况。此时,会存在 NPE 的报错
- return new StringValue(TenantContextHolder.getTenantId().toString());
+ return new StringValue(TenantContextHolder.getRequiredTenantId().toString());
}
@Override
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisKeyDefine.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisKeyDefine.java
index f90ac1246..c23bc712e 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisKeyDefine.java
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisKeyDefine.java
@@ -40,7 +40,7 @@ public class TenantRedisKeyDefine extends RedisKeyDefine {
@Override
public String formatKey(Object... args) {
- args = ArrayUtil.append(args, TenantContextHolder.getTenantId());
+ args = ArrayUtil.append(args, TenantContextHolder.getRequiredTenantId());
return super.formatKey(args);
}
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java
new file mode 100644
index 000000000..0801427da
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.framework.tenant.core.security;
+
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * 多租户 Security Web 过滤器
+ * 校验用户访问的租户,是否是其所在的租户,避免越权问题
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class TenantSecurityWebFilter extends OncePerRequestFilter {
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+ throws ServletException, IOException {
+ LoginUser user = SecurityFrameworkUtils.getLoginUser();
+ assert user != null; // shouldNotFilter 已经校验
+ if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) {
+ log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]",
+ user.getTenantId(), user.getId(), user.getUserType(),
+ TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod());
+ ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(),
+ "您无权访问该租户的数据"));
+ return;
+ }
+ // 继续过滤
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ protected boolean shouldNotFilter(HttpServletRequest request) {
+ return SecurityFrameworkUtils.getLoginUser() == null;
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/package-info.java b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/package-info.java
index 90420e01c..aa22cdb95 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/package-info.java
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/package-info.java
@@ -1,15 +1,17 @@
/**
* 多租户,支持如下层面:
* 1. DB:基于 MyBatis Plus 多租户的功能实现。
- * 2. Web:请求 HTTP API 时,解析 Header 的 tenant-id 租户编号,添加到租户上下文。
- * 3. Job:在 JobHandler 执行任务时,会按照每个租户,都独立并行执行一次。
- * 4. MQ:在 Producer 发送消息时,Header 带上 tenant-id 租户编号;在 Consumer 消费消息时,将 Header 的 tenant-id 租户编号,添加到租户上下文。
- * 5. Async:异步需要保证 ThreadLocal 的传递性,通过使用阿里开源的 TransmittableThreadLocal 实现。相关的改造点,可见:
+ * 2. Redis:通过在 Redis Key 上拼接租户编号的方式,进行隔离。
+ * 3. Web:请求 HTTP API 时,解析 Header 的 tenant-id 租户编号,添加到租户上下文。
+ * 4. Security:校验当前登陆的用户,是否越权访问其它租户的数据。
+ * 5. Job:在 JobHandler 执行任务时,会按照每个租户,都独立并行执行一次。
+ * 6. MQ:在 Producer 发送消息时,Header 带上 tenant-id 租户编号;在 Consumer 消费消息时,将 Header 的 tenant-id 租户编号,添加到租户上下文。
+ * 7. Async:异步需要保证 ThreadLocal 的传递性,通过使用阿里开源的 TransmittableThreadLocal 实现。相关的改造点,可见:
* 1)Spring Async:
* {@link cn.iocoder.yudao.framework.quartz.config.YudaoAsyncAutoConfiguration#threadPoolTaskExecutorBeanPostProcessor()}
* 2)Spring Security:
* TransmittableThreadLocalSecurityContextHolderStrategy
* 和 YudaoSecurityAutoConfiguration#securityContextHolderMethodInvokingFactoryBean() 方法
- * 6. Redis:通过在 Redis Key 上拼接租户编号的方式,进行隔离。
+ *
*/
package cn.iocoder.yudao.framework.tenant;
diff --git a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/resources/META-INF/spring.factories b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/resources/META-INF/spring.factories
index 5340df3a8..159dfcfee 100644
--- a/yudao-framework/yudao-spring-boot-starter-tenant/src/main/resources/META-INF/spring.factories
+++ b/yudao-framework/yudao-spring-boot-starter-tenant/src/main/resources/META-INF/spring.factories
@@ -2,4 +2,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantDatabaseAutoConfiguration,\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration,\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantJobAutoConfiguration,\
- cn.iocoder.yudao.framework.tenant.config.YudaoTenantMQAutoConfiguration
+ cn.iocoder.yudao.framework.tenant.config.YudaoTenantMQAutoConfiguration,\
+ cn.iocoder.yudao.framework.tenant.config.YudaoTenantSecurityAutoConfiguration