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