mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-26 09:11:52 +08:00
增加 Tenant Security 的实现
This commit is contained in:
parent
df9b06843f
commit
8795a4cdeb
@ -18,7 +18,7 @@ tenant-id: 1
|
|||||||
### 请求 /list-menus 接口 => 成功
|
### 请求 /list-menus 接口 => 成功
|
||||||
GET {{baseUrl}}/list-menus
|
GET {{baseUrl}}/list-menus
|
||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
#Authorization: Bearer 0d161f69c9ac4c7f836e1b850715a7b0
|
#Authorization: Bearer a6aa7714a2e44c95aaa8a2c5adc2a67a
|
||||||
tenant-id: 1
|
tenant-id: 1
|
||||||
|
|
||||||
### 请求 /druid/xxx 接口 => 失败 TODO 临时测试
|
### 请求 /druid/xxx 接口 => 失败 TODO 临时测试
|
||||||
|
@ -17,13 +17,15 @@ public interface WebFilterOrderEnum {
|
|||||||
|
|
||||||
// OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
|
// 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;
|
int DEMO_FILTER = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
@ -27,6 +27,11 @@
|
|||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- DB 相关 -->
|
<!-- DB 相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
@ -14,6 +14,15 @@ import java.util.Set;
|
|||||||
@Data
|
@Data
|
||||||
public class TenantProperties {
|
public class TenantProperties {
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 租户是否开启
|
||||||
|
// */
|
||||||
|
// private static final Boolean ENABLE_DEFAULT = true;
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 是否开启
|
||||||
|
// */
|
||||||
|
// private Boolean enable = ENABLE_DEFAULT;
|
||||||
/**
|
/**
|
||||||
* 需要多租户的表
|
* 需要多租户的表
|
||||||
*
|
*
|
||||||
|
@ -8,12 +8,14 @@ import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
|
|||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多租户针对 Job 的自动配置
|
* 多租户针对 Job 的自动配置
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
|
@Configuration
|
||||||
public class YudaoTenantJobAutoConfiguration {
|
public class YudaoTenantJobAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -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> tenantSecurityWebFilter() {
|
||||||
|
FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>();
|
||||||
|
registrationBean.setFilter(new TenantSecurityWebFilter());
|
||||||
|
registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);
|
||||||
|
return registrationBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,12 +4,14 @@ import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
|
|||||||
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
|
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多租户针对 Web 的自动配置
|
* 多租户针对 Web 的自动配置
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
|
@Configuration
|
||||||
public class YudaoTenantWebAutoConfiguration {
|
public class YudaoTenantWebAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -11,10 +11,28 @@ public class TenantContextHolder {
|
|||||||
|
|
||||||
private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();
|
private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得租户编号。
|
||||||
|
*
|
||||||
|
* @return 租户编号
|
||||||
|
*/
|
||||||
public static Long getTenantId() {
|
public static Long getTenantId() {
|
||||||
return TENANT_ID.get();
|
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) {
|
public static void setTenantId(Long tenantId) {
|
||||||
TENANT_ID.set(tenantId);
|
TENANT_ID.set(tenantId);
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,7 @@ public class TenantDatabaseInterceptor implements TenantLineHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Expression getTenantId() {
|
public Expression getTenantId() {
|
||||||
// TODO 芋艿:暂时不考虑获取不到的情况。此时,会存在 NPE 的报错
|
return new StringValue(TenantContextHolder.getRequiredTenantId().toString());
|
||||||
return new StringValue(TenantContextHolder.getTenantId().toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -40,7 +40,7 @@ public class TenantRedisKeyDefine extends RedisKeyDefine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String formatKey(Object... args) {
|
public String formatKey(Object... args) {
|
||||||
args = ArrayUtil.append(args, TenantContextHolder.getTenantId());
|
args = ArrayUtil.append(args, TenantContextHolder.getRequiredTenantId());
|
||||||
return super.formatKey(args);
|
return super.formatKey(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,15 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
* 多租户,支持如下层面:
|
* 多租户,支持如下层面:
|
||||||
* 1. DB:基于 MyBatis Plus 多租户的功能实现。
|
* 1. DB:基于 MyBatis Plus 多租户的功能实现。
|
||||||
* 2. Web:请求 HTTP API 时,解析 Header 的 tenant-id 租户编号,添加到租户上下文。
|
* 2. Redis:通过在 Redis Key 上拼接租户编号的方式,进行隔离。
|
||||||
* 3. Job:在 JobHandler 执行任务时,会按照每个租户,都独立并行执行一次。
|
* 3. Web:请求 HTTP API 时,解析 Header 的 tenant-id 租户编号,添加到租户上下文。
|
||||||
* 4. MQ:在 Producer 发送消息时,Header 带上 tenant-id 租户编号;在 Consumer 消费消息时,将 Header 的 tenant-id 租户编号,添加到租户上下文。
|
* 4. Security:校验当前登陆的用户,是否越权访问其它租户的数据。
|
||||||
* 5. Async:异步需要保证 ThreadLocal 的传递性,通过使用阿里开源的 TransmittableThreadLocal 实现。相关的改造点,可见:
|
* 5. Job:在 JobHandler 执行任务时,会按照每个租户,都独立并行执行一次。
|
||||||
|
* 6. MQ:在 Producer 发送消息时,Header 带上 tenant-id 租户编号;在 Consumer 消费消息时,将 Header 的 tenant-id 租户编号,添加到租户上下文。
|
||||||
|
* 7. Async:异步需要保证 ThreadLocal 的传递性,通过使用阿里开源的 TransmittableThreadLocal 实现。相关的改造点,可见:
|
||||||
* 1)Spring Async:
|
* 1)Spring Async:
|
||||||
* {@link cn.iocoder.yudao.framework.quartz.config.YudaoAsyncAutoConfiguration#threadPoolTaskExecutorBeanPostProcessor()}
|
* {@link cn.iocoder.yudao.framework.quartz.config.YudaoAsyncAutoConfiguration#threadPoolTaskExecutorBeanPostProcessor()}
|
||||||
* 2)Spring Security:
|
* 2)Spring Security:
|
||||||
* TransmittableThreadLocalSecurityContextHolderStrategy
|
* TransmittableThreadLocalSecurityContextHolderStrategy
|
||||||
* 和 YudaoSecurityAutoConfiguration#securityContextHolderMethodInvokingFactoryBean() 方法
|
* 和 YudaoSecurityAutoConfiguration#securityContextHolderMethodInvokingFactoryBean() 方法
|
||||||
* 6. Redis:通过在 Redis Key 上拼接租户编号的方式,进行隔离。
|
*
|
||||||
*/
|
*/
|
||||||
package cn.iocoder.yudao.framework.tenant;
|
package cn.iocoder.yudao.framework.tenant;
|
||||||
|
@ -2,4 +2,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
|||||||
cn.iocoder.yudao.framework.tenant.config.YudaoTenantDatabaseAutoConfiguration,\
|
cn.iocoder.yudao.framework.tenant.config.YudaoTenantDatabaseAutoConfiguration,\
|
||||||
cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration,\
|
cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration,\
|
||||||
cn.iocoder.yudao.framework.tenant.config.YudaoTenantJobAutoConfiguration,\
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user