mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 10:18:42 +08:00 
			
		
		
		
	* 【新增】后端 yudao.tenant.enable 配置项,前端 VUE_APP_TENANT_ENABLE 配置项,用于开关租户功能
				
					
				
			* 【优化】调整默认所有表开启多租户的特性,可通过 `yudao.tenant.ignore-tables` 配置项进行忽略,替代原本默认不开启的策略 * 【新增】通过 `yudao.tenant.ignore-urls` 配置忽略多租户的请求,例如说 ,例如说短信回调、支付回调等 Open API
This commit is contained in:
		| @@ -14,22 +14,28 @@ import java.util.Set; | ||||
| @Data | ||||
| public class TenantProperties { | ||||
|  | ||||
| //    /** | ||||
| //     * 租户是否开启 | ||||
| //     */ | ||||
| //    private static final Boolean ENABLE_DEFAULT = true; | ||||
| // | ||||
| //    /** | ||||
| //     * 是否开启 | ||||
| //     */ | ||||
| //    private Boolean enable = ENABLE_DEFAULT; | ||||
|     /** | ||||
|      * 租户是否开启 | ||||
|      */ | ||||
|     private static final Boolean ENABLE_DEFAULT = true; | ||||
|  | ||||
|     /** | ||||
|      * 需要多租户的表 | ||||
|      * | ||||
|      * 由于多租户并不作为 yudao 项目的重点功能,更多是扩展性的功能,所以采用正向配置需要多租户的表。 | ||||
|      * 如果需要,你可以改成 ignoreTables 来取消部分不需要的表 | ||||
|      * 是否开启 | ||||
|      */ | ||||
|     private Set<String> tables; | ||||
|     private Boolean enable = ENABLE_DEFAULT; | ||||
|  | ||||
|     /** | ||||
|      * 需要忽略多租户的请求 | ||||
|      * | ||||
|      * 默认情况下,每个请求需要带上 tenant-id 的请求头。但是,部分请求是无需带上的,例如说短信回调、支付回调等 Open API! | ||||
|      */ | ||||
|     private Set<String> ignoreUrls; | ||||
|  | ||||
|     /** | ||||
|      * 需要忽略多租户的表 | ||||
|      * | ||||
|      * 即默认所有表都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟 | ||||
|      */ | ||||
|     private Set<String> ignoreTables; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| @@ -14,6 +15,8 @@ import org.springframework.context.annotation.Configuration; | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| @EnableConfigurationProperties(TenantProperties.class) | ||||
| public class YudaoTenantDatabaseAutoConfiguration { | ||||
|  | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator; | ||||
| import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| @@ -16,6 +17,8 @@ import org.springframework.context.annotation.Configuration; | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| public class YudaoTenantJobAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| @@ -10,6 +11,8 @@ import org.springframework.context.annotation.Configuration; | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| public class YudaoTenantMQAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|   | ||||
| @@ -2,6 +2,9 @@ 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 cn.iocoder.yudao.framework.web.config.WebProperties; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| @@ -12,12 +15,16 @@ import org.springframework.context.annotation.Configuration; | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| @EnableConfigurationProperties(TenantProperties.class) | ||||
| public class YudaoTenantSecurityAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter() { | ||||
|     public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties, | ||||
|                                                                                    WebProperties webProperties) { | ||||
|         FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantSecurityWebFilter()); | ||||
|         registrationBean.setFilter(new TenantSecurityWebFilter(tenantProperties, webProperties)); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| @@ -12,6 +13,8 @@ import org.springframework.context.annotation.Configuration; | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| public class YudaoTenantWebAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|   | ||||
| @@ -33,7 +33,7 @@ public class TenantContextHolder { | ||||
|     public static Long getRequiredTenantId() { | ||||
|         Long tenantId = getTenantId(); | ||||
|         if (tenantId == null) { | ||||
|             throw new NullPointerException("TenantContextHolder 不存在租户编号"); | ||||
|             throw new NullPointerException("TenantContextHolder 不存在租户编号"); // TODO 芋艿:增加文档链接 | ||||
|         } | ||||
|         return tenantId; | ||||
|     } | ||||
|   | ||||
| @@ -3,8 +3,6 @@ package cn.iocoder.yudao.framework.tenant.core.db; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.tenant.config.TenantProperties; | ||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfo; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; | ||||
| import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; | ||||
| import lombok.AllArgsConstructor; | ||||
| import net.sf.jsqlparser.expression.Expression; | ||||
| @@ -27,13 +25,7 @@ public class TenantDatabaseInterceptor implements TenantLineHandler { | ||||
|  | ||||
|     @Override | ||||
|     public boolean ignoreTable(String tableName) { | ||||
|         // 如果实体类继承 TenantBaseDO 类,则是多租户表,不进行忽略 | ||||
|         TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName); | ||||
|         if (tableInfo != null && TenantBaseDO.class.isAssignableFrom(tableInfo.getEntityType())) { | ||||
|             return false; | ||||
|         } | ||||
|         // 不包含,说明要过滤 | ||||
|         return !CollUtil.contains(properties.getTables(), tableName); | ||||
|         return CollUtil.contains(properties.getIgnoreTables(), tableName); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,13 +1,17 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.security; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| 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.config.TenantProperties; | ||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
| import cn.iocoder.yudao.framework.web.config.WebProperties; | ||||
| import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
| import org.springframework.util.AntPathMatcher; | ||||
|  | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| @@ -18,34 +22,72 @@ import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 多租户 Security Web 过滤器 | ||||
|  * 校验用户访问的租户,是否是其所在的租户,避免越权问题 | ||||
|  * 1. 如果是登陆的用户,校验是否有权限访问该租户,避免越权问题。 | ||||
|  * 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。 | ||||
|  * | ||||
|  * 校验用户访问的租户,是否是其所在的租户, | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class TenantSecurityWebFilter extends OncePerRequestFilter { | ||||
| public class TenantSecurityWebFilter extends ApiRequestFilter { | ||||
|  | ||||
|     private final TenantProperties tenantProperties; | ||||
|     private final AntPathMatcher pathMatcher; | ||||
|  | ||||
|     public TenantSecurityWebFilter(TenantProperties tenantProperties, | ||||
|                                    WebProperties webProperties) { | ||||
|         super(webProperties); | ||||
|         this.tenantProperties = tenantProperties; | ||||
|         this.pathMatcher = new AntPathMatcher(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||||
|             throws ServletException, IOException { | ||||
|         Long tenantId = TenantContextHolder.getTenantId(); | ||||
|         // 1. 登陆的用户,校验是否有权限访问该租户,避免越权问题。 | ||||
|         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(), | ||||
|                     "您无权访问该租户的数据")); | ||||
|         if (user != null) { | ||||
|             // 如果获取不到租户编号,则尝试使用登陆用户的租户编号 | ||||
|             if (tenantId == null) { | ||||
|                 tenantId = user.getTenantId(); | ||||
|                 TenantContextHolder.setTenantId(tenantId); | ||||
|             // 如果传递了租户编号,则进行比对租户编号,避免越权问题 | ||||
|             } else 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; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。 | ||||
|         if (tenantId == null && !isIgnoreUrl(request)) { | ||||
|             log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod()); | ||||
|             ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), | ||||
|                     "租户的请求未传递,请进行排查")); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 继续过滤 | ||||
|         chain.doFilter(request, response); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected boolean shouldNotFilter(HttpServletRequest request) { | ||||
|         return SecurityFrameworkUtils.getLoginUser() == null; | ||||
|     private boolean isIgnoreUrl(HttpServletRequest request) { | ||||
|         // 快速匹配,保证性能 | ||||
|         if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) { | ||||
|             return true; | ||||
|         } | ||||
|         // 逐个 Ant 路径匹配 | ||||
|         for (String url : tenantProperties.getIgnoreUrls()) { | ||||
|             if (pathMatcher.match(url, request.getRequestURI())) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV