|
@@ -0,0 +1,112 @@
|
|
|
+package com.usky.common.tenant.core.security;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.CollUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.usky.common.core.bean.ApiResult;
|
|
|
+import com.usky.common.core.util.ServletUtils;
|
|
|
+import com.usky.common.security.utils.SecurityUtils;
|
|
|
+import com.usky.common.tenant.config.TenantProperties;
|
|
|
+import com.usky.common.tenant.core.context.TenantContextHolder;
|
|
|
+import com.usky.common.tenant.core.service.TenantFrameworkService;
|
|
|
+import com.usky.system.model.LoginUser;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.util.AntPathMatcher;
|
|
|
+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 过滤器
|
|
|
+ * 1. 如果是登陆的用户,校验是否有权限访问该租户,避免越权问题。
|
|
|
+ * 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。
|
|
|
+ * 3. 校验租户是合法,例如说被禁用、到期
|
|
|
+ *
|
|
|
+ * 校验用户访问的租户,是否是其所在的租户,
|
|
|
+ *
|
|
|
+ * @author yq
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+public class TenantSecurityWebFilter extends OncePerRequestFilter {
|
|
|
+
|
|
|
+ private final TenantProperties tenantProperties;
|
|
|
+
|
|
|
+ private final AntPathMatcher pathMatcher;
|
|
|
+
|
|
|
+ private final TenantFrameworkService tenantFrameworkService;
|
|
|
+
|
|
|
+ public TenantSecurityWebFilter(TenantProperties tenantProperties,
|
|
|
+ TenantFrameworkService tenantFrameworkService) {
|
|
|
+ this.tenantProperties = tenantProperties;
|
|
|
+ this.pathMatcher = new AntPathMatcher();
|
|
|
+ this.tenantFrameworkService = tenantFrameworkService;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
|
|
+ throws ServletException, IOException {
|
|
|
+ Integer tenantId = TenantContextHolder.getTenantId();
|
|
|
+ // 1. 登陆的用户,校验是否有权限访问该租户,避免越权问题。
|
|
|
+ LoginUser user = SecurityUtils.getLoginUser();
|
|
|
+ if (user != null) {
|
|
|
+ // 如果获取不到租户编号,则尝试使用登陆用户的租户编号
|
|
|
+ if (tenantId == null) {
|
|
|
+ tenantId = user.getTenantId();
|
|
|
+ TenantContextHolder.setTenantId(tenantId);
|
|
|
+ // 如果传递了租户编号,则进行比对租户编号,避免越权问题
|
|
|
+ } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) {
|
|
|
+ String msg = "您无权访问该租户的数据";
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(ApiResult.error(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), msg)));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果非允许忽略租户的 URL,则校验租户是否合法
|
|
|
+ if (!isIgnoreUrl(request)) {
|
|
|
+ // 2. 如果请求未带租户的编号,不允许访问。
|
|
|
+ if (tenantId == null) {
|
|
|
+ String msg = "租户的请求未传递,请进行排查";
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(ApiResult.error(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), msg)));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 3. 校验租户是合法,例如说被禁用、到期
|
|
|
+ try {
|
|
|
+ ApiResult<Void> validResult = tenantFrameworkService.validTenant(tenantId);
|
|
|
+ if (ApiResult.ResultStatus.ERROR.equals(validResult.getStatus())){
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(ApiResult.error(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), validResult.getMsg())));
|
|
|
+ }
|
|
|
+ } catch (Throwable ex) {
|
|
|
+ String msg = "系统异常"+ex.getMessage();
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(ApiResult.error(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), msg)));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else { // 如果是允许忽略租户的 URL,若未传递租户编号,则默认忽略租户编号,避免报错
|
|
|
+ if (tenantId == null) {
|
|
|
+ TenantContextHolder.setIgnore(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 继续过滤
|
|
|
+ chain.doFilter(request, response);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|