From 761dbf03437ac97ce2161b9d8e4c4d80edcf0921 Mon Sep 17 00:00:00 2001 From: JEECG <445654970@qq.com> Date: Thu, 16 Oct 2025 23:05:33 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90sa-token=E3=80=91=E5=A4=9A=E7=A7=9F?= =?UTF-8?q?=E6=88=B7=E7=9A=84check=E5=92=8C=E7=BA=BF=E7=A8=8B=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jeecg/common/util/LoginUserUtils.java | 2 +- .../org/jeecg/common/util/TokenUtils.java | 26 +++++ .../jeecg/config/satoken/SaTokenConfig.java | 104 +++++++++++++++++- 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/LoginUserUtils.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/LoginUserUtils.java index c92701aef..f96f6f325 100644 --- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/LoginUserUtils.java +++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/LoginUserUtils.java @@ -143,7 +143,7 @@ public class LoginUserUtils { loginUser.setUserIdentity(null); // 用户身份 loginUser.setPost(null); // 职务 loginUser.setTelephone(null); // 座机 - loginUser.setClientId(null); // 设备ID + loginUser.setRelTenantIds(null); // 关联租户 loginUser.setMainDepPostId(null); // 主岗位 StpUtil.getSession().set(SESSION_KEY_LOGIN_USER, loginUser); diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java index a2c31362c..93bee3268 100644 --- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java +++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java @@ -124,4 +124,30 @@ public class TokenUtils { return true; } + /** + * 获取登录用户 + * + * @param commonApi + * @param username + * @return + */ + public static LoginUser getLoginUser(String username, CommonAPI commonApi, RedisUtil redisUtil) { + LoginUser loginUser = null; + String loginUserKey = CacheConstant.SYS_USERS_CACHE + "::" + username; + //【重要】此处通过redis原生获取缓存用户,是为了解决微服务下system服务挂了,其他服务互调不通问题--- + if (redisUtil.hasKey(loginUserKey)) { + try { + loginUser = (LoginUser) redisUtil.get(loginUserKey); + //解密用户 + SensitiveInfoUtil.handlerObject(loginUser, false); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } else { + // 查询用户信息 + loginUser = commonApi.getUserByName(username); + } + return loginUser; + } + } diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/satoken/SaTokenConfig.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/satoken/SaTokenConfig.java index 25d2ab159..fd9f0e494 100644 --- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/satoken/SaTokenConfig.java +++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/satoken/SaTokenConfig.java @@ -2,6 +2,7 @@ package org.jeecg.config.satoken; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.filter.SaServletFilter; import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.jwt.StpLogicJwtForSimple; @@ -12,9 +13,14 @@ import cn.dev33.satoken.stp.StpUtil; import jakarta.annotation.Resource; import jakarta.servlet.DispatcherType; import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.api.CommonAPI; +import org.jeecg.common.config.TenantContext; +import org.jeecg.common.constant.CacheConstant; import org.jeecg.common.constant.CommonConstant; -import org.jeecg.common.util.oConvertUtils; +import org.jeecg.common.system.vo.LoginUser; +import org.jeecg.common.util.*; import org.jeecg.config.JeecgBaseConfig; +import org.jeecg.config.mybatis.MybatisPlusSaasConfig; import org.jeecg.config.satoken.ignore.InMemoryIgnoreAuth; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; @@ -44,9 +50,12 @@ public class SaTokenConfig implements WebMvcConfigurer { @Resource private JeecgBaseConfig jeecgBaseConfig; - @Autowired private Environment env; + @Autowired + private CommonAPI commonAPI; + @Autowired + private RedisUtil redisUtil; /** * Sa-Token 整合 jwt (Simple 模式) @@ -150,6 +159,9 @@ public class SaTokenConfig implements WebMvcConfigurer { // 最终校验登录状态 StpUtil.checkLogin(); + + // 租户校验逻辑 + checkTenantAuthorization(); }) // 异常处理函数:每次认证函数发生异常时执行此函数 .setError(e -> { @@ -157,9 +169,7 @@ public class SaTokenConfig implements WebMvcConfigurer { log.warn("请求路径: {}, Method: {},Token: {}", SaHolder.getRequest().getRequestPath(), SaHolder.getRequest().getMethod(), StpUtil.getTokenValue()); // 返回401状态码 - SaHolder.getResponse() - .setStatus(401) - .setHeader("Content-Type", "application/json;charset=UTF-8"); + SaHolder.getResponse().setStatus(401).setHeader("Content-Type", "application/json;charset=UTF-8"); return org.jeecg.common.system.util.JwtUtil.responseErrorJson(401, CommonConstant.TOKEN_IS_INVALID_MSG); }) // 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入) @@ -185,6 +195,11 @@ public class SaTokenConfig implements WebMvcConfigurer { SaRouter.match(SaHttpMethod.OPTIONS).free(r2 -> { SaHolder.getResponse().setStatus(HttpStatus.OK.value()); }); + + // 每次请求前,获取请求头中的租户ID,放入当前线程上下文 + String tenantId = SaHolder.getRequest().getHeader(CommonConstant.TENANT_ID); + TenantContext.setTenant(tenantId); + log.info("===【TenantContext 线程设置】=== 请求路径: {}, 租户ID: {}", SaHolder.getRequest().getRequestPath(), tenantId); }); } @@ -322,5 +337,84 @@ public class SaTokenConfig implements WebMvcConfigurer { return excludeUrls; } + + /** + * 校验用户的tenant_id和前端传过来的是否一致 + * + *

实现逻辑: + *

+ * + * @throws NotLoginException 租户授权变更异常 + */ + private void checkTenantAuthorization() { + log.debug("------ 租户校验开始 ------"); + // 如果未开启租户控制,直接返回 + if (!MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) { + return; + } + + try { + // 获取当前登录用户信息 + LoginUser loginUser = TokenUtils.getLoginUser(LoginUserUtils.getUsername(), commonAPI, redisUtil); + if (loginUser == null) { + return; + } + + String username = loginUser.getUsername(); + String userTenantIds = loginUser.getRelTenantIds(); + + // 如果用户未配置租户信息,直接返回 + if (oConvertUtils.isEmpty(userTenantIds)) { + return; + } + + // 获取前端请求头中的租户ID + String loginTenantId = TokenUtils.getTenantIdByRequest(SpringContextUtils.getHttpServletRequest()); + log.info("登录租户:{}", loginTenantId); + log.info("用户拥有那些租户:{}", userTenantIds); + + // 登录用户无租户,前端header中租户ID值为 0 + String str = "0"; + if (oConvertUtils.isEmpty(loginTenantId) || str.equals(loginTenantId)) { + return; + } + + String[] userTenantIdsArray = userTenantIds.split(","); + if (!oConvertUtils.isIn(loginTenantId, userTenantIdsArray)) { + boolean isAuthorization = false; + + //======================================================================== + // 查询用户信息(如果租户不匹配从数据库中重新查询一次用户信息) + String loginUserKey = CacheConstant.SYS_USERS_CACHE + "::" + username; + redisUtil.del(loginUserKey); + + LoginUser loginUserFromDb = commonAPI.getUserByName(username); + LoginUserUtils.setSessionUser(loginUserFromDb); + if (loginUserFromDb != null && oConvertUtils.isNotEmpty(loginUserFromDb.getRelTenantIds())) { + String[] newArray = loginUserFromDb.getRelTenantIds().split(","); + if (oConvertUtils.isIn(loginTenantId, newArray)) { + isAuthorization = true; + } + } + //======================================================================== + + if (!isAuthorization) { + log.info("租户异常——登录租户:{}", loginTenantId); + log.info("租户异常——用户拥有租户组:{}", userTenantIds); + throw new NotLoginException("登录租户授权变更,请重新登陆!", StpUtil.TYPE, NotLoginException.KICK_OUT); + } + } + + }catch (Exception e) { + log.error("租户校验异常:{}", e.getMessage(), e); + } + } }