From 8cef3227f3c8302c82f6e09c2353a6267188478a Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Thu, 12 Mar 2026 19:45:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=E5=A4=9A=E7=A7=9F=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/OAuth2AccessTokenCheckRespDTO.java | 15 ++-- .../framework/common/enums/DocumentEnum.java | 8 +- .../rdms-spring-boot-starter-rpc/README.md | 1 - .../README.md | 3 - .../framework/security/core/LoginUser.java | 38 -------- .../filter/TokenAuthenticationFilter.java | 61 +++---------- .../service/SecurityFrameworkServiceImpl.java | 38 +------- .../core/util/SecurityFrameworkUtils.java | 79 +--------------- .../rdms-spring-boot-starter-web/README.md | 2 +- .../config/RdmsSwaggerAutoConfiguration.java | 87 +++--------------- .../web/core/util/WebFrameworkUtils.java | 80 +---------------- .../README.md | 2 +- .../core/util/WebSocketFrameworkUtils.java | 37 -------- rdms-gateway/README.md | 1 - .../gateway/filter/grey/GrayLoadBalancer.java | 2 +- .../filter/logging/AccessLogFilter.java | 54 +++++------ .../gateway/filter/security/LoginUser.java | 26 ------ .../security/TokenAuthenticationFilter.java | 89 +++++-------------- .../handler/GlobalExceptionHandler.java | 4 +- .../rdms/gateway/util/WebFrameworkUtils.java | 62 +------------ .../system/enums/ErrorCodeConstants.java | 14 --- .../system/enums/permission/RoleCodeEnum.java | 13 +-- .../system/api/oauth2/OAuth2TokenApiImpl.java | 8 +- .../controller/admin/auth/AuthController.http | 6 -- .../admin/dict/DictDataController.http | 1 - .../admin/file/FileConfigController.http | 6 +- .../controller/admin/ip/AreaController.http | 2 - .../admin/logger/OperateLogController.http | 1 - .../admin/oauth2/OAuth2ClientController.http | 1 - .../admin/oauth2/OAuth2OpenController.http | 9 -- .../admin/oauth2/OAuth2UserController.http | 2 - .../vo/open/OAuth2OpenCheckTokenRespVO.java | 19 ++-- .../admin/permission/MenuController.http | 1 - .../admin/permission/MenuController.java | 17 ++-- .../admin/permission/RoleController.http | 5 -- .../admin/redis/RedisController.http | 1 - .../controller/admin/user/UserController.http | 3 - .../admin/user/UserProfileController.http | 1 - .../service/permission/MenuService.java | 70 --------------- .../service/permission/MenuServiceImpl.java | 7 -- 40 files changed, 123 insertions(+), 753 deletions(-) diff --git a/rdms-framework/rdms-common/src/main/java/com/njcn/rdms/framework/common/biz/system/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java b/rdms-framework/rdms-common/src/main/java/com/njcn/rdms/framework/common/biz/system/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java index cb6fe8c..cdc0ff1 100644 --- a/rdms-framework/rdms-common/src/main/java/com/njcn/rdms/framework/common/biz/system/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java +++ b/rdms-framework/rdms-common/src/main/java/com/njcn/rdms/framework/common/biz/system/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java @@ -8,26 +8,23 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; -@Schema(description = "RPC 服务 - OAuth2 访问令牌的校验 Response DTO") +@Schema(description = "RPC service - OAuth2 access token check response") @Data public class OAuth2AccessTokenCheckRespDTO implements Serializable { - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @Schema(description = "User id", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") private Long userId; - @Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @Schema(description = "User type", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer userType; - @Schema(description = "用户信息", example = "{\"nickname\": \"灿能\"}") + @Schema(description = "User info", example = "{\"nickname\": \"rdms\"}") private Map userInfo; - @Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long tenantId; - - @Schema(description = "授权范围的数组", example = "user_info") + @Schema(description = "Scopes", example = "user_info") private List scopes; - @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "Expire time", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime expiresTime; } diff --git a/rdms-framework/rdms-common/src/main/java/com/njcn/rdms/framework/common/enums/DocumentEnum.java b/rdms-framework/rdms-common/src/main/java/com/njcn/rdms/framework/common/enums/DocumentEnum.java index 0b96d3e..2c6204f 100644 --- a/rdms-framework/rdms-common/src/main/java/com/njcn/rdms/framework/common/enums/DocumentEnum.java +++ b/rdms-framework/rdms-common/src/main/java/com/njcn/rdms/framework/common/enums/DocumentEnum.java @@ -3,17 +3,11 @@ package com.njcn.rdms.framework.common.enums; import lombok.AllArgsConstructor; import lombok.Getter; -/** - * 文档地址 - * - * @author hongawen - */ @Getter @AllArgsConstructor public enum DocumentEnum { - REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"), - TENANT("https://doc.iocoder.cn", "SaaS 多租户文档"); + REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"); private final String url; private final String memo; diff --git a/rdms-framework/rdms-spring-boot-starter-rpc/README.md b/rdms-framework/rdms-spring-boot-starter-rpc/README.md index d69129a..c2c9d31 100644 --- a/rdms-framework/rdms-spring-boot-starter-rpc/README.md +++ b/rdms-framework/rdms-spring-boot-starter-rpc/README.md @@ -71,7 +71,6 @@ - Token 透传 - 用户信息透传 - TraceId / 请求链路标识透传 -- 租户编号透传 常见做法是基于 `RequestInterceptor`,在所有 Feign 请求发出前统一补充请求头。 diff --git a/rdms-framework/rdms-spring-boot-starter-security/README.md b/rdms-framework/rdms-spring-boot-starter-security/README.md index 9a38eb4..4263e6d 100644 --- a/rdms-framework/rdms-spring-boot-starter-security/README.md +++ b/rdms-framework/rdms-spring-boot-starter-security/README.md @@ -106,10 +106,8 @@ - `id` - `userType` - `info` -- `tenantId` - `scopes` - `expiresTime` -- `visitTenantId` 认证成功后,`LoginUser` 会进入 Spring Security 上下文,后续可以通过 `SecurityFrameworkUtils` 获取,例如: @@ -646,4 +644,3 @@ public Long createUser(UserSaveReqVO reqVO) { - 原始 token 透传 - 更细的权限表达式 - 更完整的审计日志字段 -- 更严格的跨租户访问控制 diff --git a/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/LoginUser.java b/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/LoginUser.java index 733190e..2f31094 100644 --- a/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/LoginUser.java +++ b/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/LoginUser.java @@ -2,65 +2,27 @@ package com.njcn.rdms.framework.security.core; import cn.hutool.core.map.MapUtil; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.njcn.rdms.framework.common.enums.UserTypeEnum; import lombok.Data; -import lombok.experimental.Accessors; import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; -/** - * 登录用户信息 - * - * @author hongawen - */ @Data public class LoginUser { public static final String INFO_KEY_NICKNAME = "nickname"; public static final String INFO_KEY_DEPT_ID = "deptId"; - /** - * 用户编号 - */ private Long id; - /** - * 用户类型 - * - * 关联 {@link UserTypeEnum} - */ private Integer userType; - /** - * 额外的用户信息 - */ private Map info; - /** - * 租户编号 - */ - private Long tenantId; - /** - * 授权范围 - */ private List scopes; - /** - * 过期时间 - */ private LocalDateTime expiresTime; - // ========== 上下文 ========== - /** - * 上下文字段,不进行持久化 - * - * 1. 用于基于 LoginUser 维度的临时缓存 - */ @JsonIgnore private Map context; - /** - * 访问的租户编号 - */ - private Long visitTenantId; public void setContext(String key, Object value) { if (context == null) { diff --git a/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/filter/TokenAuthenticationFilter.java b/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/filter/TokenAuthenticationFilter.java index 7031475..7c5737b 100644 --- a/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/filter/TokenAuthenticationFilter.java @@ -26,42 +26,29 @@ import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -/** - * Token 过滤器,验证 token 的有效性 - * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 - * - * @author hongawen - */ @RequiredArgsConstructor @Slf4j public class TokenAuthenticationFilter extends OncePerRequestFilter { private final SecurityProperties securityProperties; - private final GlobalExceptionHandler globalExceptionHandler; - private final OAuth2TokenCommonApi oauth2TokenApi; @Override @SuppressWarnings("NullableProblems") protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - // 情况一,基于 header[login-user] 获得用户,例如说来自 Gateway 或者其它服务透传 LoginUser loginUser = buildLoginUserByHeader(request); - // 情况二,基于 Token 获得用户 - // 注意,这里主要满足直接使用 Nginx 直接转发到 Spring Cloud 服务的场景。 if (loginUser == null) { String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader(), securityProperties.getTokenParameter()); if (StrUtil.isNotEmpty(token)) { Integer userType = WebFrameworkUtils.getLoginUserType(request); try { - // 1.1 基于 token 构建登录用户 loginUser = buildLoginUserByToken(token, userType); - // 1.2 模拟 Login 功能,方便日常开发调试 if (loginUser == null) { - loginUser = mockLoginUser(request, token, userType); + loginUser = mockLoginUser(token, userType); } } catch (Throwable ex) { CommonResult result = globalExceptionHandler.allExceptionHandler(request, ex); @@ -71,61 +58,40 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { } } - // 设置当前用户 if (loginUser != null) { SecurityFrameworkUtils.setLoginUser(loginUser, request); } - // 继续过滤链 chain.doFilter(request, response); } private LoginUser buildLoginUserByToken(String token, Integer userType) { try { - // 校验访问令牌 OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData(); if (accessToken == null) { return null; } - // 用户类型不匹配,无权限 - // 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型 - // 类似 WebSocket 的 /ws/* 连接地址,是不需要比对用户类型的 - if (userType != null - && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { + if (userType != null && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { throw new AccessDeniedException("错误的用户类型"); } - // 构建登录用户 - return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) - .setInfo(accessToken.getUserInfo()) // 额外的用户信息 - .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()) + return new LoginUser().setId(accessToken.getUserId()) + .setUserType(accessToken.getUserType()) + .setInfo(accessToken.getUserInfo()) + .setScopes(accessToken.getScopes()) .setExpiresTime(accessToken.getExpiresTime()); } catch (ServiceException serviceException) { - // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 return null; } } - /** - * 模拟登录用户,方便日常开发调试 - * - * 注意,在线上环境下,一定要关闭该功能!!! - * - * @param request 请求 - * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号 - * @param userType 用户类型 - * @return 模拟的 LoginUser - */ - private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) { + private LoginUser mockLoginUser(String token, Integer userType) { if (!securityProperties.getMockEnable()) { return null; } - // 必须以 mockSecret 开头 if (!token.startsWith(securityProperties.getMockSecret())) { return null; } - // 构建模拟用户 Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length())); - return new LoginUser().setId(userId).setUserType(userType) - .setTenantId(WebFrameworkUtils.getTenantId(request)); + return new LoginUser().setId(userId).setUserType(userType); } private LoginUser buildLoginUserByHeader(HttpServletRequest request) { @@ -134,20 +100,15 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { return null; } try { - loginUserStr = URLDecoder.decode(loginUserStr, StandardCharsets.UTF_8); // 解码,解决中文乱码问题 + loginUserStr = URLDecoder.decode(loginUserStr, StandardCharsets.UTF_8); LoginUser loginUser = JsonUtils.parseObject(loginUserStr, LoginUser.class); - // 用户类型不匹配,无权限 - // 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型 - // 类似 WebSocket 的 /ws/* 连接地址,是不需要比对用户类型的 Integer userType = WebFrameworkUtils.getLoginUserType(request); - if (userType != null - && loginUser != null - && ObjectUtil.notEqual(loginUser.getUserType(), userType)) { + if (userType != null && loginUser != null && ObjectUtil.notEqual(loginUser.getUserType(), userType)) { throw new AccessDeniedException("错误的用户类型"); } return loginUser; } catch (Exception ex) { - log.error("[buildLoginUserByHeader][解析 LoginUser({}) 发生异常]", loginUserStr, ex); ; + log.error("[buildLoginUserByHeader][parse LoginUser({}) error]", loginUserStr, ex); throw ex; } } diff --git a/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/service/SecurityFrameworkServiceImpl.java b/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/service/SecurityFrameworkServiceImpl.java index e2ae7e4..5d6d230 100644 --- a/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/service/SecurityFrameworkServiceImpl.java +++ b/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/service/SecurityFrameworkServiceImpl.java @@ -16,44 +16,28 @@ import java.util.List; import static com.njcn.rdms.framework.common.util.cache.CacheUtils.buildCache; import static com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; -import static com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck; -/** - * 默认的 {@link SecurityFrameworkService} 实现类 - * - * @author hongawen - */ @AllArgsConstructor public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { private final PermissionCommonApi permissionApi; - /** - * 针对 {@link #hasAnyRoles(String...)} 的缓存 - */ private final LoadingCache>, Boolean> hasAnyRolesCache = buildCache( - Duration.ofMinutes(1L), // 过期时间 1 分钟 + Duration.ofMinutes(1L), new CacheLoader>, Boolean>() { - @Override public Boolean load(KeyValue> key) { return permissionApi.hasAnyRoles(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData(); } - }); - /** - * 针对 {@link #hasAnyPermissions(String...)} 的缓存 - */ private final LoadingCache>, Boolean> hasAnyPermissionsCache = buildCache( - Duration.ofMinutes(1L), // 过期时间 1 分钟 + Duration.ofMinutes(1L), new CacheLoader>, Boolean>() { - @Override public Boolean load(KeyValue> key) { return permissionApi.hasAnyPermissions(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData(); } - }); @Override @@ -64,12 +48,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { @Override @SneakyThrows public boolean hasAnyPermissions(String... permissions) { - // 特殊:跨租户访问 - if (skipPermissionCheck()) { - return true; - } - - // 权限校验 Long userId = getLoginUserId(); if (userId == null) { return false; @@ -85,12 +63,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { @Override @SneakyThrows public boolean hasAnyRoles(String... roles) { - // 特殊:跨租户访问 - if (skipPermissionCheck()) { - return true; - } - - // 权限校验 Long userId = getLoginUserId(); if (userId == null) { return false; @@ -105,12 +77,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { @Override public boolean hasAnyScopes(String... scope) { - // 特殊:跨租户访问 - if (skipPermissionCheck()) { - return true; - } - - // 权限校验 LoginUser user = SecurityFrameworkUtils.getLoginUser(); if (user == null) { return false; diff --git a/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/util/SecurityFrameworkUtils.java b/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/util/SecurityFrameworkUtils.java index 98f81ca..4382fda 100644 --- a/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/util/SecurityFrameworkUtils.java +++ b/rdms-framework/rdms-spring-boot-starter-security/src/main/java/com/njcn/rdms/framework/security/core/util/SecurityFrameworkUtils.java @@ -1,7 +1,6 @@ package com.njcn.rdms.framework.security.core.util; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import com.njcn.rdms.framework.security.core.LoginUser; import com.njcn.rdms.framework.web.core.util.WebFrameworkUtils; @@ -16,33 +15,14 @@ import org.springframework.util.StringUtils; import java.util.Collections; -/** - * 安全服务工具类 - * - * @author hongawen - */ public class SecurityFrameworkUtils { - /** - * HEADER 认证头 value 的前缀 - */ public static final String AUTHORIZATION_BEARER = "Bearer"; - public static final String LOGIN_USER_HEADER = "login-user"; private SecurityFrameworkUtils() {} - /** - * 从请求中,获得认证 Token - * - * @param request 请求 - * @param headerName 认证 Token 对应的 Header 名字 - * @param parameterName 认证 Token 对应的 Parameter 名字 - * @return 认证 Token - */ - public static String obtainAuthorization(HttpServletRequest request, - String headerName, String parameterName) { - // 1. 获得 Token。优先级:Header > Parameter + public static String obtainAuthorization(HttpServletRequest request, String headerName, String parameterName) { String token = request.getHeader(headerName); if (StrUtil.isEmpty(token)) { token = request.getParameter(parameterName); @@ -50,16 +30,10 @@ public class SecurityFrameworkUtils { if (!StringUtils.hasText(token)) { return null; } - // 2. 去除 Token 中带的 Bearer int index = token.indexOf(AUTHORIZATION_BEARER + " "); return index >= 0 ? token.substring(index + 7).trim() : token; } - /** - * 获得当前认证信息 - * - * @return 认证信息 - */ public static Authentication getAuthentication() { SecurityContext context = SecurityContextHolder.getContext(); if (context == null) { @@ -68,11 +42,6 @@ public class SecurityFrameworkUtils { return context.getAuthentication(); } - /** - * 获取当前用户 - * - * @return 当前用户 - */ @Nullable public static LoginUser getLoginUser() { Authentication authentication = getAuthentication(); @@ -82,52 +51,28 @@ public class SecurityFrameworkUtils { return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null; } - /** - * 获得当前用户的编号,从上下文中 - * - * @return 用户编号 - */ @Nullable public static Long getLoginUserId() { LoginUser loginUser = getLoginUser(); return loginUser != null ? loginUser.getId() : null; } - /** - * 获得当前用户的昵称,从上下文中 - * - * @return 昵称 - */ @Nullable public static String getLoginUserNickname() { LoginUser loginUser = getLoginUser(); return loginUser != null ? MapUtil.getStr(loginUser.getInfo(), LoginUser.INFO_KEY_NICKNAME) : null; } - /** - * 获得当前用户的部门编号,从上下文中 - * - * @return 部门编号 - */ @Nullable public static Long getLoginUserDeptId() { LoginUser loginUser = getLoginUser(); return loginUser != null ? MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DEPT_ID) : null; } - /** - * 设置当前用户 - * - * @param loginUser 登录用户 - * @param request 请求 - */ public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { - // 创建 Authentication,并设置到上下文 Authentication authentication = buildAuthentication(loginUser, request); SecurityContextHolder.getContext().setAuthentication(authentication); - // 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号; - // 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息 if (request != null) { WebFrameworkUtils.setLoginUserId(request, loginUser.getId()); WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType()); @@ -135,28 +80,10 @@ public class SecurityFrameworkUtils { } private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { - // 创建 UsernamePasswordAuthenticationToken 对象 - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( - loginUser, null, Collections.emptyList()); + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); return authenticationToken; } - /** - * 是否条件跳过权限校验,包括数据权限、功能权限 - * - * @return 是否跳过 - */ - public static boolean skipPermissionCheck() { - LoginUser loginUser = getLoginUser(); - if (loginUser == null) { - return false; - } - if (loginUser.getVisitTenantId() == null) { - return false; - } - // 重点:跨租户访问时,无法进行权限校验 - return ObjUtil.notEqual(loginUser.getVisitTenantId(), loginUser.getTenantId()); - } - } diff --git a/rdms-framework/rdms-spring-boot-starter-web/README.md b/rdms-framework/rdms-spring-boot-starter-web/README.md index d325626..e0ee4c5 100644 --- a/rdms-framework/rdms-spring-boot-starter-web/README.md +++ b/rdms-framework/rdms-spring-boot-starter-web/README.md @@ -224,7 +224,7 @@ private RestTemplate loadBalancedRestTemplate; - `/admin-api/**` - `/app-api/**` -同时会在文档中预置常见请求头,例如认证头、租户头,便于在 Swagger 页面直接调试接口。 +同时会在文档中预置常见请求头,例如认证头,便于在 Swagger 页面直接调试接口。 Swagger 配置示例: diff --git a/rdms-framework/rdms-spring-boot-starter-web/src/main/java/com/njcn/rdms/framework/swagger/config/RdmsSwaggerAutoConfiguration.java b/rdms-framework/rdms-spring-boot-starter-web/src/main/java/com/njcn/rdms/framework/swagger/config/RdmsSwaggerAutoConfiguration.java index e48caf4..40daccb 100644 --- a/rdms-framework/rdms-spring-boot-starter-web/src/main/java/com/njcn/rdms/framework/swagger/config/RdmsSwaggerAutoConfiguration.java +++ b/rdms-framework/rdms-spring-boot-starter-web/src/main/java/com/njcn/rdms/framework/swagger/config/RdmsSwaggerAutoConfiguration.java @@ -6,7 +6,6 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; -import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.security.SecurityRequirement; @@ -34,42 +33,24 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import static com.njcn.rdms.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; - -/** - * Swagger 自动配置类,基于 OpenAPI + Springdoc 实现。 - * - * 友情提示: - * 1. Springdoc 文档地址:仓库 - * 2. Swagger 规范,于 2015 更名为 OpenAPI 规范,本质是一个东西 - * - * @author hongawen - */ -@AutoConfiguration(before = Knife4jAutoConfiguration.class) // before 原因,保证覆写的 Knife4jOpenApiCustomizer 先生效!相关 https://github.com/YunaiV/ruoyi-vue-pro/issues/954 讨论 +@AutoConfiguration(before = Knife4jAutoConfiguration.class) @ConditionalOnClass({OpenAPI.class}) @EnableConfigurationProperties(SwaggerProperties.class) -@ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用 +@ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) @Import(Knife4jOpenApiCustomizer.class) public class RdmsSwaggerAutoConfiguration { - // ========== 全局 OpenAPI 配置 ========== - @Bean public OpenAPI createApi(SwaggerProperties properties) { Map securitySchemas = buildSecuritySchemes(); OpenAPI openAPI = new OpenAPI() - // 接口信息 .info(buildInfo(properties)) - // 接口安全配置 .components(new Components().securitySchemes(securitySchemas)) .addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION)); securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key))); return openAPI; } - /** - * API 摘要信息 - */ private Info buildInfo(SwaggerProperties properties) { return new Info() .title(properties.getTitle()) @@ -79,24 +60,18 @@ public class RdmsSwaggerAutoConfiguration { .license(new License().name(properties.getLicense()).url(properties.getLicenseUrl())); } - /** - * 安全模式,这里配置通过请求头 Authorization 传递 token 参数 - */ private Map buildSecuritySchemes() { Map securitySchemes = new HashMap<>(); SecurityScheme securityScheme = new SecurityScheme() - .type(SecurityScheme.Type.APIKEY) // 类型 - .name(HttpHeaders.AUTHORIZATION) // 请求头的 name - .in(SecurityScheme.In.HEADER); // token 所在位置 + .type(SecurityScheme.Type.APIKEY) + .name(HttpHeaders.AUTHORIZATION) + .in(SecurityScheme.In.HEADER); securitySchemes.put(HttpHeaders.AUTHORIZATION, securityScheme); return securitySchemes; } - /** - * 自定义 OpenAPI 处理器 - */ @Bean - @Primary // 目的:以我们创建的 OpenAPIService Bean 为主,避免一键改包后,启动报错! + @Primary public OpenAPIService openApiBuilder(Optional openAPI, SecurityService securityParser, SpringDocConfigProperties springDocConfigProperties, @@ -108,11 +83,6 @@ public class RdmsSwaggerAutoConfiguration { propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider); } - // ========== 分组 OpenAPI 配置 ========== - - /** - * 所有模块的 API 分组 - */ @Bean public GroupedOpenApi allGroupedOpenApi() { return buildGroupedOpenApi("all", ""); @@ -127,60 +97,29 @@ public class RdmsSwaggerAutoConfiguration { .group(group) .pathsToMatch("/admin-api/" + path + "/**", "/app-api/" + path + "/**") .addOperationCustomizer((operation, handlerMethod) -> operation - .addParametersItem(buildTenantHeaderParameter()) .addParametersItem(buildSecurityHeaderParameter())) .addOperationCustomizer(buildOperationIdCustomizer()) .build(); } - /** - * 构建 Tenant 租户编号请求头参数 - * - * @return 多租户参数 - */ - private static Parameter buildTenantHeaderParameter() { - return new Parameter() - .name(HEADER_TENANT_ID) // header 名 - .description("租户编号") // 描述 - .in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header - .schema(new IntegerSchema()._default(1L).name(HEADER_TENANT_ID).description("租户编号")); // 默认:使用租户编号为 1 - } - - /** - * 构建 Authorization 认证请求头参数 - * - * 解决 Knife4j Authorize 未生效,请求header里未包含参数 - * - * @return 认证参数 - */ private static Parameter buildSecurityHeaderParameter() { return new Parameter() - .name(HttpHeaders.AUTHORIZATION) // header 名 - .description("认证 Token") // 描述 - .in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header - .schema(new StringSchema()._default("Bearer test1").name(HEADER_TENANT_ID).description("认证 Token")); // 默认:使用用户编号为 1 + .name(HttpHeaders.AUTHORIZATION) + .description("认证 Token") + .in(String.valueOf(SecurityScheme.In.HEADER)) + .schema(new StringSchema()._default("Bearer test1") + .name(HttpHeaders.AUTHORIZATION) + .description("认证 Token")); } - /** - * 核心:自定义OperationId生成规则,组合「类名前缀 + 方法名」 - * - * @see app-api 前缀不生效,都是使用 admin-api - */ private static OperationCustomizer buildOperationIdCustomizer() { return (operation, handlerMethod) -> { - // 1. 获取控制器类名(如 UserController) String className = handlerMethod.getBeanType().getSimpleName(); - // 2. 提取类名前缀(去除 Controller 后缀,如 UserController -> User) String classPrefix = className.replaceAll("Controller$", ""); - // 3. 获取方法名(如 list) String methodName = handlerMethod.getMethod().getName(); - // 4. 组合生成 operationId(如 User_list) - String operationId = classPrefix + "_" + methodName; - // 5. 设置自定义 operationId - operation.setOperationId(operationId); + operation.setOperationId(classPrefix + "_" + methodName); return operation; }; } } - diff --git a/rdms-framework/rdms-spring-boot-starter-web/src/main/java/com/njcn/rdms/framework/web/core/util/WebFrameworkUtils.java b/rdms-framework/rdms-spring-boot-starter-web/src/main/java/com/njcn/rdms/framework/web/core/util/WebFrameworkUtils.java index b712040..f8772b6 100644 --- a/rdms-framework/rdms-spring-boot-starter-web/src/main/java/com/njcn/rdms/framework/web/core/util/WebFrameworkUtils.java +++ b/rdms-framework/rdms-spring-boot-starter-web/src/main/java/com/njcn/rdms/framework/web/core/util/WebFrameworkUtils.java @@ -12,26 +12,12 @@ import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -/** - * 专属于 web 包的工具类 - * - * @author hongawen - */ public class WebFrameworkUtils { private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = "login_user_id"; private static final String REQUEST_ATTRIBUTE_LOGIN_USER_TYPE = "login_user_type"; - private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result"; - public static final String HEADER_TENANT_ID = "tenant-id"; - public static final String HEADER_VISIT_TENANT_ID = "visit-tenant-id"; - - /** - * 终端的 Header - * - * @see TerminalEnum - */ public static final String HEADER_TERMINAL = "terminal"; private static WebProperties properties; @@ -40,51 +26,14 @@ public class WebFrameworkUtils { WebFrameworkUtils.properties = webProperties; } - /** - * 获得租户编号,从 header 中 - * 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供 - * - * @param request 请求 - * @return 租户编号 - */ - public static Long getTenantId(HttpServletRequest request) { - String tenantId = request.getHeader(HEADER_TENANT_ID); - return NumberUtil.isNumber(tenantId) ? Long.valueOf(tenantId) : null; - } - - /** - * 获得访问的租户编号,从 header 中 - * 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供 - * - * @param request 请求 - * @return 租户编号 - */ - public static Long getVisitTenantId(HttpServletRequest request) { - String tenantId = request.getHeader(HEADER_VISIT_TENANT_ID); - return NumberUtil.isNumber(tenantId)? Long.valueOf(tenantId) : null; - } - public static void setLoginUserId(ServletRequest request, Long userId) { request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId); } - /** - * 设置用户类型 - * - * @param request 请求 - * @param userType 用户类型 - */ public static void setLoginUserType(ServletRequest request, Integer userType) { request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType); } - /** - * 获得当前用户的编号,从请求中 - * 注意:该方法仅限于 framework 框架使用!!! - * - * @param request 请求 - * @return 用户编号 - */ public static Long getLoginUserId(HttpServletRequest request) { if (request == null) { return null; @@ -92,23 +41,14 @@ public class WebFrameworkUtils { return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID); } - /** - * 获得当前用户的类型 - * 注意:该方法仅限于 web 相关的 framework 组件使用!!! - * - * @param request 请求 - * @return 用户编号 - */ public static Integer getLoginUserType(HttpServletRequest request) { if (request == null) { return null; } - // 1. 优先,从 Attribute 中获取 Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE); if (userType != null) { return userType; } - // 2. 其次,基于 URL 前缀的约定 if (request.getServletPath().startsWith(properties.getAdminApi().getPrefix())) { return UserTypeEnum.ADMIN.getValue(); } @@ -119,13 +59,11 @@ public class WebFrameworkUtils { } public static Integer getLoginUserType() { - HttpServletRequest request = getRequest(); - return getLoginUserType(request); + return getLoginUserType(getRequest()); } public static Long getLoginUserId() { - HttpServletRequest request = getRequest(); - return getLoginUserId(request); + return getLoginUserId(getRequest()); } public static Integer getTerminal() { @@ -155,24 +93,10 @@ public class WebFrameworkUtils { return servletRequestAttributes.getRequest(); } - /** - * 判断是否为 RPC 请求 - * - * @param request 请求 - * @return 是否为 RPC 请求 - */ public static boolean isRpcRequest(HttpServletRequest request) { return request.getRequestURI().startsWith(RpcConstants.RPC_API_PREFIX); } - /** - * 判断是否为 RPC 请求 - * - * 约定大于配置,只要以 Api 结尾,都认为是 RPC 接口 - * - * @param className 类名 - * @return 是否为 RPC 请求 - */ public static boolean isRpcRequest(String className) { return className.endsWith("Api"); } diff --git a/rdms-framework/rdms-spring-boot-starter-websocket/README.md b/rdms-framework/rdms-spring-boot-starter-websocket/README.md index 2847cbb..66c1153 100644 --- a/rdms-framework/rdms-spring-boot-starter-websocket/README.md +++ b/rdms-framework/rdms-spring-boot-starter-websocket/README.md @@ -75,7 +75,7 @@ - 需要前端通过 `?token={token}` 形式携带令牌 - `LoginUserHandshakeInterceptor` 会将 `LoginUser` 写入 Session -- `WebSocketFrameworkUtils` 可获取 `userId/userType/tenantId` +- `WebSocketFrameworkUtils` 可获取 `userId/userType` ## 4. 开发人员上手 diff --git a/rdms-framework/rdms-spring-boot-starter-websocket/src/main/java/com/njcn/rdms/framework/websocket/core/util/WebSocketFrameworkUtils.java b/rdms-framework/rdms-spring-boot-starter-websocket/src/main/java/com/njcn/rdms/framework/websocket/core/util/WebSocketFrameworkUtils.java index 4aab667..abe612e 100644 --- a/rdms-framework/rdms-spring-boot-starter-websocket/src/main/java/com/njcn/rdms/framework/websocket/core/util/WebSocketFrameworkUtils.java +++ b/rdms-framework/rdms-spring-boot-starter-websocket/src/main/java/com/njcn/rdms/framework/websocket/core/util/WebSocketFrameworkUtils.java @@ -5,63 +5,26 @@ import org.springframework.web.socket.WebSocketSession; import java.util.Map; -/** - * 专属于 web 包的工具类 - * - * @author hongawen - */ public class WebSocketFrameworkUtils { public static final String ATTRIBUTE_LOGIN_USER = "LOGIN_USER"; - /** - * 设置当前用户 - * - * @param loginUser 登录用户 - * @param attributes Session - */ public static void setLoginUser(LoginUser loginUser, Map attributes) { attributes.put(ATTRIBUTE_LOGIN_USER, loginUser); } - /** - * 获取当前用户 - * - * @return 当前用户 - */ public static LoginUser getLoginUser(WebSocketSession session) { return (LoginUser) session.getAttributes().get(ATTRIBUTE_LOGIN_USER); } - /** - * 获得当前用户的编号 - * - * @return 用户编号 - */ public static Long getLoginUserId(WebSocketSession session) { LoginUser loginUser = getLoginUser(session); return loginUser != null ? loginUser.getId() : null; } - /** - * 获得当前用户的类型 - * - * @return 用户编号 - */ public static Integer getLoginUserType(WebSocketSession session) { LoginUser loginUser = getLoginUser(session); return loginUser != null ? loginUser.getUserType() : null; } - /** - * 获得当前用户的租户编号 - * - * @param session Session - * @return 租户编号 - */ - public static Long getTenantId(WebSocketSession session) { - LoginUser loginUser = getLoginUser(session); - return loginUser != null ? loginUser.getTenantId() : null; - } - } diff --git a/rdms-gateway/README.md b/rdms-gateway/README.md index db8146a..cd6beb8 100644 --- a/rdms-gateway/README.md +++ b/rdms-gateway/README.md @@ -96,7 +96,6 @@ spring: ``` Authorization: Bearer -tenant-id: ``` 网关校验成功后,会透传 `login-user` 给后端服务。 diff --git a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/grey/GrayLoadBalancer.java b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/grey/GrayLoadBalancer.java index 490fb6c..19fefce 100644 --- a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/grey/GrayLoadBalancer.java +++ b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/grey/GrayLoadBalancer.java @@ -79,7 +79,7 @@ public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer { // 基于 tag 过滤实例列表 chooseInstances = filterTagServiceInstances(chooseInstances, headers); - // 随机 + 权重获取实例列表 TODO 芋艿:目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法 + // 随机 + 权重获取实例列表 TODO 目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法 return new DefaultResponse(NacosBalancer.getHostByRandomWeight3(chooseInstances)); } diff --git a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/logging/AccessLogFilter.java b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/logging/AccessLogFilter.java index 25d8940..23c576b 100644 --- a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/logging/AccessLogFilter.java +++ b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/logging/AccessLogFilter.java @@ -50,7 +50,7 @@ import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER; * * 从功能上,它类似 rdms-spring-boot-starter-web 的 ApiAccessLogFilter 过滤器 * - * TODO 芋艿:如果网关执行异常,不会记录访问日志,后续研究下 https://github.com/Silvmike/webflux-demo/blob/master/tests/src/test/java/ru/hardcoders/demo/webflux/web_handler/filters/logging + * TODO 如果网关执行异常,不会记录访问日志,后续研究下 https://github.com/Silvmike/webflux-demo/blob/master/tests/src/test/java/ru/hardcoders/demo/webflux/web_handler/filters/logging * * @author hongawen */ @@ -71,34 +71,34 @@ public class AccessLogFilter implements GlobalFilter, Ordered { // log.info("[writeAccessLog][日志内容:{}]", JsonUtils.toJsonString(gatewayLog)); // 方式二:调用远程服务,记录到数据库中 - // TODO 芋艿:暂未实现 + // TODO 暂未实现 // 方式三:打印到控制台,方便排查错误 - try { - Map values = MapUtil.newHashMap(15, true); // 手工拼接,保证排序;15 保证不用扩容 - values.put("userId", gatewayLog.getUserId()); - values.put("userType", gatewayLog.getUserType()); - values.put("routeId", gatewayLog.getRoute() != null ? gatewayLog.getRoute().getId() : null); - values.put("schema", gatewayLog.getSchema()); - values.put("requestUrl", gatewayLog.getRequestUrl()); - values.put("queryParams", gatewayLog.getQueryParams().toSingleValueMap()); - values.put("requestBody", JsonUtils.isJson(gatewayLog.getRequestBody()) ? // 保证 body 的展示好看 - JSONUtil.parse(gatewayLog.getRequestBody()) : gatewayLog.getRequestBody()); - values.put("requestHeaders", JsonUtils.toJsonString(gatewayLog.getRequestHeaders().toSingleValueMap())); - values.put("userIp", gatewayLog.getUserIp()); - values.put("responseBody", JsonUtils.isJson(gatewayLog.getResponseBody()) ? // 保证 body 的展示好看 - JSONUtil.parse(gatewayLog.getResponseBody()) : gatewayLog.getResponseBody()); - values.put("responseHeaders", gatewayLog.getResponseHeaders() != null ? - JsonUtils.toJsonString(gatewayLog.getResponseHeaders().toSingleValueMap()) : null); - values.put("httpStatus", gatewayLog.getHttpStatus()); - values.put("startTime", LocalDateTimeUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMATTER)); - values.put("endTime", LocalDateTimeUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMATTER)); - values.put("duration", gatewayLog.getDuration() != null ? gatewayLog.getDuration() + " ms" : null); - log.info("[writeAccessLog][网关日志:{}]", JsonUtils.toJsonPrettyString(values)); - } catch (Exception e) { - // 兜底处理,参见 https://gitee.com/zhijiantianya/rdms-cloud/issues/IC9A70 - log.error("[writeAccessLog][打印网关日志时,发生异常]", e); - } +// try { +// Map values = MapUtil.newHashMap(15, true); // 手工拼接,保证排序;15 保证不用扩容 +// values.put("userId", gatewayLog.getUserId()); +// values.put("userType", gatewayLog.getUserType()); +// values.put("routeId", gatewayLog.getRoute() != null ? gatewayLog.getRoute().getId() : null); +// values.put("schema", gatewayLog.getSchema()); +// values.put("requestUrl", gatewayLog.getRequestUrl()); +// values.put("queryParams", gatewayLog.getQueryParams().toSingleValueMap()); +// values.put("requestBody", JsonUtils.isJson(gatewayLog.getRequestBody()) ? // 保证 body 的展示好看 +// JSONUtil.parse(gatewayLog.getRequestBody()) : gatewayLog.getRequestBody()); +// values.put("requestHeaders", JsonUtils.toJsonString(gatewayLog.getRequestHeaders().toSingleValueMap())); +// values.put("userIp", gatewayLog.getUserIp()); +// values.put("responseBody", JsonUtils.isJson(gatewayLog.getResponseBody()) ? // 保证 body 的展示好看 +// JSONUtil.parse(gatewayLog.getResponseBody()) : gatewayLog.getResponseBody()); +// values.put("responseHeaders", gatewayLog.getResponseHeaders() != null ? +// JsonUtils.toJsonString(gatewayLog.getResponseHeaders().toSingleValueMap()) : null); +// values.put("httpStatus", gatewayLog.getHttpStatus()); +// values.put("startTime", LocalDateTimeUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMATTER)); +// values.put("endTime", LocalDateTimeUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMATTER)); +// values.put("duration", gatewayLog.getDuration() != null ? gatewayLog.getDuration() + " ms" : null); +// log.info("[writeAccessLog][网关日志:{}]", JsonUtils.toJsonPrettyString(values)); +// } catch (Exception e) { +// // 兜底处理,参见 https://gitee.com/zhijiantianya/rdms-cloud/issues/IC9A70 +// log.error("[writeAccessLog][打印网关日志时,发生异常]", e); +// } } @Override diff --git a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/security/LoginUser.java b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/security/LoginUser.java index 3af5408..5219b3c 100644 --- a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/security/LoginUser.java +++ b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/security/LoginUser.java @@ -6,39 +6,13 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; -/** - * 登录用户信息 - * - * copy from rdms-spring-boot-starter-security 的 LoginUser 类 - * - * @author hongawen - */ @Data public class LoginUser { - /** - * 用户编号 - */ private Long id; - /** - * 用户类型 - */ private Integer userType; - /** - * 额外的用户信息 - */ private Map info; - /** - * 租户编号 - */ - private Long tenantId; - /** - * 授权范围 - */ private List scopes; - /** - * 过期时间 - */ private LocalDateTime expiresTime; } diff --git a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/security/TokenAuthenticationFilter.java b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/security/TokenAuthenticationFilter.java index be04cdc..ee20dfa 100644 --- a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/security/TokenAuthenticationFilter.java +++ b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/filter/security/TokenAuthenticationFilter.java @@ -6,12 +6,10 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.njcn.rdms.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; import com.njcn.rdms.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO; -import com.njcn.rdms.framework.common.core.KeyValue; import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.util.date.LocalDateTimeUtils; import com.njcn.rdms.framework.common.util.json.JsonUtils; import com.njcn.rdms.gateway.util.SecurityFrameworkUtils; -import com.njcn.rdms.gateway.util.WebFrameworkUtils; import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; @@ -28,141 +26,102 @@ import java.util.function.Function; import static com.njcn.rdms.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache; -/** - * Token 过滤器,验证 token 的有效性 - * 1. 验证通过时,将 userId、userType、tenantId 通过 Header 转发给服务 - * 2. 验证不通过,还是会转发给服务。因为,接口是否需要登录的校验,还是交给服务自身处理 - * - * @author hongawen - */ @Component public class TokenAuthenticationFilter implements GlobalFilter, Ordered { - /** - * CommonResult 对应的 TypeReference 结果,用于解析 checkToken 的结果 - */ - private static final TypeReference> CHECK_RESULT_TYPE_REFERENCE - = new TypeReference>() {}; + private static final TypeReference> CHECK_RESULT_TYPE_REFERENCE = + new TypeReference>() {}; - /** - * 空的 LoginUser 的结果 - * - * 用于解决如下问题: - * 1. {@link #getLoginUser(ServerWebExchange, String)} 返回 Mono.empty() 时,会导致后续的 flatMap 无法进行处理的问题。 - * 2. {@link #buildUser(String)} 时,如果 Token 已经过期,返回 LOGIN_USER_EMPTY 对象,避免缓存无法刷新 - */ private static final LoginUser LOGIN_USER_EMPTY = new LoginUser(); private final WebClient webClient; - /** - * 登录用户的本地缓存 - * - * key1:多租户的编号 - * key2:访问令牌 - */ - private final LoadingCache, LoginUser> loginUserCache = buildAsyncReloadingCache(Duration.ofMinutes(1), - new CacheLoader, LoginUser>() { + private final LoadingCache loginUserCache = buildAsyncReloadingCache(Duration.ofMinutes(1), + new CacheLoader() { @Override - public LoginUser load(KeyValue token) { - String body = checkAccessToken(token.getKey(), token.getValue()).block(); + public LoginUser load(String token) { + String body = checkAccessToken(token).block(); return buildUser(body); } }); public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) { - // Q:为什么不使用 OAuth2TokenApi 进行调用? - // A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support - // A2:校验 Token 的 API 需要使用到 header[tenant-id] 传递租户编号,暂时不想编写 RequestInterceptor 实现 - // 因此,这里采用 WebClient,通过 lbFunction 实现负载均衡 this.webClient = WebClient.builder().filter(lbFunction).build(); } @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // 移除 login-user 的请求头,避免伪造模拟 exchange = SecurityFrameworkUtils.removeLoginUser(exchange); - // 情况一,如果没有 Token 令牌,则直接继续 filter String token = SecurityFrameworkUtils.obtainAuthorization(exchange); if (StrUtil.isEmpty(token)) { return chain.filter(exchange); } - // 情况二,如果有 Token 令牌,则解析对应 userId、userType、tenantId 等字段,并通过 通过 Header 转发给服务 - // 重要说明:defaultIfEmpty 作用,保证 Mono.empty() 情况,可以继续执行 `flatMap 的 chain.filter(exchange)` 逻辑,避免返回给前端空的 Response!! ServerWebExchange finalExchange = exchange; - return getLoginUser(exchange, token).defaultIfEmpty(LOGIN_USER_EMPTY).flatMap(user -> { - // 1. 无用户,直接 filter 继续请求 - if (user == LOGIN_USER_EMPTY || // 下面 expiresTime 的判断,为了解决 token 实际已经过期的情况 - user.getExpiresTime() == null || LocalDateTimeUtils.beforeNow(user.getExpiresTime())) { + return getLoginUser(token).defaultIfEmpty(LOGIN_USER_EMPTY).flatMap(user -> { + if (user == LOGIN_USER_EMPTY + || user.getExpiresTime() == null + || LocalDateTimeUtils.beforeNow(user.getExpiresTime())) { return chain.filter(finalExchange); } - // 2.1 有用户,则设置登录用户 SecurityFrameworkUtils.setLoginUser(finalExchange, user); - // 2.2 将 user 并设置到 login-user 的请求头,使用 json 存储值 ServerWebExchange newExchange = finalExchange.mutate() - .request(builder -> SecurityFrameworkUtils.setLoginUserHeader(builder, user)).build(); + .request(builder -> SecurityFrameworkUtils.setLoginUserHeader(builder, user)) + .build(); return chain.filter(newExchange); }); } - private Mono getLoginUser(ServerWebExchange exchange, String token) { - // 从缓存中,获取 LoginUser - Long tenantId = WebFrameworkUtils.getTenantId(exchange); - KeyValue cacheKey = new KeyValue().setKey(tenantId).setValue(token); - LoginUser localUser = loginUserCache.getIfPresent(cacheKey); + private Mono getLoginUser(String token) { + LoginUser localUser = loginUserCache.getIfPresent(token); if (localUser != null) { return Mono.just(localUser); } - // 缓存不存在,则请求远程服务 - return checkAccessToken(tenantId, token).flatMap((Function>) body -> { + return checkAccessToken(token).flatMap((Function>) body -> { LoginUser remoteUser = buildUser(body); if (remoteUser != null) { - // 非空,则进行缓存 - loginUserCache.put(cacheKey, remoteUser); + loginUserCache.put(token, remoteUser); return Mono.just(remoteUser); } return Mono.empty(); }); } - private Mono checkAccessToken(Long tenantId, String token) { + private Mono checkAccessToken(String token) { return webClient.get() .uri(OAuth2TokenCommonApi.URL_CHECK, uriBuilder -> uriBuilder.queryParam("accessToken", token).build()) - .headers(httpHeaders -> WebFrameworkUtils.setTenantIdHeader(tenantId, httpHeaders)) // 设置租户的 Header - .retrieve().bodyToMono(String.class); + .retrieve() + .bodyToMono(String.class); } private LoginUser buildUser(String body) { - // 处理结果,结果不正确 CommonResult result = JsonUtils.parseObject(body, CHECK_RESULT_TYPE_REFERENCE); if (result == null) { return null; } if (result.isError()) { - // 特殊情况:令牌已经过期(code = 401),需要返回 LOGIN_USER_EMPTY,避免 Token 一直因为缓存,被误判为有效 if (Objects.equals(result.getCode(), HttpStatus.UNAUTHORIZED.value())) { return LOGIN_USER_EMPTY; } return null; } - // 创建登录用户 OAuth2AccessTokenCheckRespDTO tokenInfo = result.getData(); - return new LoginUser().setId(tokenInfo.getUserId()).setUserType(tokenInfo.getUserType()) - .setInfo(tokenInfo.getUserInfo()) // 额外的用户信息 - .setTenantId(tokenInfo.getTenantId()).setScopes(tokenInfo.getScopes()) + return new LoginUser().setId(tokenInfo.getUserId()) + .setUserType(tokenInfo.getUserType()) + .setInfo(tokenInfo.getUserInfo()) + .setScopes(tokenInfo.getScopes()) .setExpiresTime(tokenInfo.getExpiresTime()); } @Override public int getOrder() { - return -100; // 和 Spring Security Filter 的顺序对齐 + return -100; } } diff --git a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/handler/GlobalExceptionHandler.java b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/handler/GlobalExceptionHandler.java index e244393..803a635 100644 --- a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/handler/GlobalExceptionHandler.java +++ b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/handler/GlobalExceptionHandler.java @@ -52,7 +52,7 @@ public class GlobalExceptionHandler implements ErrorWebExceptionHandler { */ private CommonResult responseStatusExceptionHandler(ServerWebExchange exchange, ResponseStatusException ex) { - // TODO 芋艿:这里要精细化翻译,默认返回用户是看不懂的 + // TODO 这里要精细化翻译,默认返回用户是看不懂的 ServerHttpRequest request = exchange.getRequest(); log.error("[responseStatusExceptionHandler][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex); return CommonResult.error(ex.getStatusCode().value(), ex.getReason()); @@ -66,7 +66,7 @@ public class GlobalExceptionHandler implements ErrorWebExceptionHandler { Throwable ex) { ServerHttpRequest request = exchange.getRequest(); log.error("[defaultExceptionHandler][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex); - // TODO 芋艿:是否要插入异常日志呢? + // TODO 是否要插入异常日志呢? // 返回 ERROR CommonResult return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg()); } diff --git a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/util/WebFrameworkUtils.java b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/util/WebFrameworkUtils.java index 8472a5e..c0449ca 100644 --- a/rdms-gateway/src/main/java/com/njcn/rdms/gateway/util/WebFrameworkUtils.java +++ b/rdms-gateway/src/main/java/com/njcn/rdms/gateway/util/WebFrameworkUtils.java @@ -2,91 +2,45 @@ package com.njcn.rdms.gateway.util; import cn.hutool.core.net.NetUtil; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.NumberUtil; import cn.hutool.extra.servlet.ServletUtil; import com.njcn.rdms.framework.common.util.json.JsonUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -/** - * Web 工具类 - * - * copy from rdms-spring-boot-starter-web 的 WebFrameworkUtils 类 - * - * @author hongawen - */ @Slf4j public class WebFrameworkUtils { - private static final String HEADER_TENANT_ID = "tenant-id"; - private WebFrameworkUtils() {} - /** - * 将 Gateway 请求中的 header,设置到 HttpHeaders 中 - * - * @param tenantId 租户编号 - * @param httpHeaders WebClient 的请求 - */ - public static void setTenantIdHeader(Long tenantId, HttpHeaders httpHeaders) { - if (tenantId == null) { - return; - } - httpHeaders.set(HEADER_TENANT_ID, String.valueOf(tenantId)); - } - - public static Long getTenantId(ServerWebExchange exchange) { - String tenantId = exchange.getRequest().getHeaders().getFirst(HEADER_TENANT_ID); - return NumberUtil.isNumber(tenantId) ? Long.valueOf(tenantId) : null; - } - - /** - * 返回 JSON 字符串 - * - * @param exchange 响应 - * @param object 对象,会序列化成 JSON 字符串 - */ - @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 + @SuppressWarnings("deprecation") public static Mono writeJSON(ServerWebExchange exchange, Object object) { - // 设置 header ServerHttpResponse response = exchange.getResponse(); response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8); - // 设置 body return response.writeWith(Mono.fromSupplier(() -> { DataBufferFactory bufferFactory = response.bufferFactory(); try { return bufferFactory.wrap(JsonUtils.toJsonByte(object)); } catch (Exception ex) { ServerHttpRequest request = exchange.getRequest(); - log.error("[writeJSON][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex); + log.error("[writeJSON][uri({}/{}) error]", request.getURI(), request.getMethod(), ex); return bufferFactory.wrap(new byte[0]); } })); } - /** - * 获得客户端 IP - * - * 参考 {@link ServletUtil} 的 getClientIP 方法 - * - * @param exchange 请求 - * @param otherHeaderNames 其它 header 名字的数组 - * @return 客户端 IP - */ public static String getClientIP(ServerWebExchange exchange, String... otherHeaderNames) { - String[] headers = { "X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR" }; + String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", + "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; if (ArrayUtil.isNotEmpty(otherHeaderNames)) { headers = ArrayUtil.addAll(headers, otherHeaderNames); } - // 方式一,通过 header 获取 String ip; for (String header : headers) { ip = exchange.getRequest().getHeaders().getFirst(header); @@ -94,8 +48,6 @@ public class WebFrameworkUtils { return NetUtil.getMultistageReverseProxyIp(ip); } } - - // 方式二,通过 remoteAddress 获取 if (exchange.getRequest().getRemoteAddress() == null) { return null; } @@ -103,12 +55,6 @@ public class WebFrameworkUtils { return NetUtil.getMultistageReverseProxyIp(ip); } - /** - * 获得请求匹配的 Route 路由 - * - * @param exchange 请求 - * @return 路由 - */ public static Route getGatewayRoute(ServerWebExchange exchange) { return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); } diff --git a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java index c5674d9..f0118d9 100644 --- a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java +++ b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java @@ -100,20 +100,6 @@ public interface ErrorCodeConstants { ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量"); ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁"); - // ========== 租户信息 1-002-015-000 ========== - ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在"); - ErrorCode TENANT_DISABLE = new ErrorCode(1_002_015_001, "名字为【{}】的租户已被禁用"); - ErrorCode TENANT_EXPIRE = new ErrorCode(1_002_015_002, "名字为【{}】的租户已过期"); - ErrorCode TENANT_CAN_NOT_UPDATE_SYSTEM = new ErrorCode(1_002_015_003, "系统租户不能进行修改、删除等操作!"); - ErrorCode TENANT_NAME_DUPLICATE = new ErrorCode(1_002_015_004, "名字为【{}】的租户已存在"); - ErrorCode TENANT_WEBSITE_DUPLICATE = new ErrorCode(1_002_015_005, "域名为【{}】的租户已存在"); - - // ========== 租户套餐 1-002-016-000 ========== - ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1_002_016_000, "租户套餐不存在"); - ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1_002_016_001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除"); - ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1_002_016_002, "名字为【{}】的租户套餐已被禁用"); - ErrorCode TENANT_PACKAGE_NAME_DUPLICATE = new ErrorCode(1_002_016_003, "已经存在该名字的租户套餐"); - // ========== OAuth2 客户端 1-002-020-000 ========= ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在"); ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1_002_020_001, "OAuth2 客户端编号已存在"); diff --git a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/permission/RoleCodeEnum.java b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/permission/RoleCodeEnum.java index 0de970b..67d647e 100644 --- a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/permission/RoleCodeEnum.java +++ b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/permission/RoleCodeEnum.java @@ -4,25 +4,14 @@ import com.njcn.rdms.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; import lombok.Getter; -/** - * 角色标识枚举 - */ @Getter @AllArgsConstructor public enum RoleCodeEnum { SUPER_ADMIN("super_admin", "超级管理员"), - TENANT_ADMIN("tenant_admin", "租户管理员"), - CRM_ADMIN("crm_admin", "CRM 管理员"); // CRM 系统专用 - ; + CRM_ADMIN("crm_admin", "CRM 管理员"); - /** - * 角色编码 - */ private final String code; - /** - * 名字 - */ private final String name; public static boolean isSuperAdmin(String code) { diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/oauth2/OAuth2TokenApiImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/oauth2/OAuth2TokenApiImpl.java index 8ccf964..959ca7b 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/oauth2/OAuth2TokenApiImpl.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/oauth2/OAuth2TokenApiImpl.java @@ -1,12 +1,11 @@ package com.njcn.rdms.module.system.api.oauth2; import com.njcn.rdms.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; -import com.njcn.rdms.framework.common.pojo.CommonResult; -import com.njcn.rdms.framework.common.util.object.BeanUtils; - import com.njcn.rdms.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO; import com.njcn.rdms.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCreateReqDTO; import com.njcn.rdms.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenRespDTO; +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import com.njcn.rdms.module.system.service.oauth2.OAuth2TokenService; import jakarta.annotation.Resource; @@ -15,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController; import static com.njcn.rdms.framework.common.pojo.CommonResult.success; -@RestController // 提供 RESTful API 接口,给 Feign 调用 +@RestController @Validated public class OAuth2TokenApiImpl implements OAuth2TokenCommonApi { @@ -30,7 +29,6 @@ public class OAuth2TokenApiImpl implements OAuth2TokenCommonApi { } @Override - // 访问令牌校验时,无需传递租户编号;主要解决上传文件的场景,前端不会传递 tenant-id public CommonResult checkAccessToken(String accessToken) { OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(accessToken); return success(BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenCheckRespDTO.class)); diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/AuthController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/AuthController.http index 52a724b..a274ec8 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/AuthController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/AuthController.http @@ -1,7 +1,6 @@ ### 请求 /login 接口 => 成功 POST {{baseUrl}}/system/auth/login Content-Type: application/json -tenant-id: {{adminTenantId}} tag: Yunai.local { @@ -14,7 +13,6 @@ tag: Yunai.local ### 请求 /login 接口【加密 AES】 => 成功 POST {{baseUrl}}/system/auth/login Content-Type: application/json -tenant-id: {{adminTenantId}} tag: Yunai.local X-API-ENCRYPT: true @@ -23,7 +21,6 @@ WvSX9MOrenyGfBhEM0g1/hHgq8ocktMZ9OwAJ6MOG5FUrzYF/rG5JF1eMptQM1wT73VgDS05l/37WeRt ### 请求 /login 接口【加密 RSA】 => 成功 POST {{baseUrl}}/system/auth/login Content-Type: application/json -tenant-id: {{adminTenantId}} tag: Yunai.local X-API-ENCRYPT: true @@ -32,7 +29,6 @@ e7QZTork9ZV5CmgZvSd+cHZk3xdUxKtowLM02kOha+gxHK2H/daU8nVBYS3+bwuDRy5abf+Pz1QJJGVA ### 请求 /login 接口 => 成功(无验证码) POST {{baseUrl}}/system/auth/login Content-Type: application/json -tenant-id: {{adminTenantId}} { "username": "admin", @@ -42,10 +38,8 @@ tenant-id: {{adminTenantId}} ### 请求 /get-permission-info 接口 => 成功 GET {{baseUrl}}/system/auth/get-permission-info Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} ### 请求 /list-menus 接口 => 成功 GET {{baseUrl}}/system/list-menus Authorization: Bearer {{token}} #Authorization: Bearer a6aa7714a2e44c95aaa8a2c5adc2a67a -tenant-id: {{adminTenantId}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dict/DictDataController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dict/DictDataController.http index 5a7ce8e..f7890dc 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dict/DictDataController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dict/DictDataController.http @@ -1,4 +1,3 @@ ### 请求 /menu/list 接口 => 成功 GET {{baseUrl}}/system/dict-data/list-all-simple Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileConfigController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileConfigController.http index 002de56..1275a3d 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileConfigController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileConfigController.http @@ -1,7 +1,6 @@ -### 请求 /system/file-config/create 接口 => 成功 +### 请求 /system/file-config/create 接口 => 成功 POST {{baseUrl}}/system/file-config/create Content-Type: application/json -tenant-id: {{adminTenantId}} Authorization: Bearer {{token}} { @@ -21,7 +20,6 @@ Authorization: Bearer {{token}} ### 请求 /system/file-config/update 接口 => 成功 PUT {{baseUrl}}/system/file-config/update Content-Type: application/json -tenant-id: {{adminTenantId}} Authorization: Bearer {{token}} { @@ -41,6 +39,4 @@ Authorization: Bearer {{token}} ### 请求 /system/file-config/test 接口 => 成功 GET {{baseUrl}}/system/file-config/test?id=2 Content-Type: application/json -tenant-id: {{adminTenantId}} Authorization: Bearer {{token}} - diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/ip/AreaController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/ip/AreaController.http index 1416561..f6a9cc6 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/ip/AreaController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/ip/AreaController.http @@ -1,5 +1,3 @@ ### 获得地区树 GET {{baseUrl}}/system/area/tree Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/OperateLogController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/OperateLogController.http index be3102e..9fdccac 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/OperateLogController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/OperateLogController.http @@ -1,4 +1,3 @@ ### 请求 /system/operate-log/page 接口 => 成功 GET {{baseUrl}}/system/operate-log/page Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2ClientController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2ClientController.http index 7830fa2..3ffe945 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2ClientController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2ClientController.http @@ -2,7 +2,6 @@ POST {{baseUrl}}/system/oauth2-client/create Content-Type: application/json Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} { "id": "1", diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2OpenController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2OpenController.http index cdcebbf..b6564bc 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2OpenController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2OpenController.http @@ -1,13 +1,11 @@ ### 请求 /system/oauth2/authorize 接口 => 成功 GET {{baseUrl}}/system/oauth2/authorize?clientId=default Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} ### 请求 /system/oauth2/authorize + token 接口 => 成功 POST {{baseUrl}}/system/oauth2/authorize Content-Type: application/x-www-form-urlencoded Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true @@ -15,7 +13,6 @@ response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=htt POST {{baseUrl}}/system/oauth2/authorize Content-Type: application/x-www-form-urlencoded Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=false @@ -23,7 +20,6 @@ response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=http POST {{baseUrl}}/system/oauth2/token Content-Type: application/x-www-form-urlencoded Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== -tenant-id: {{adminTenantId}} grant_type=authorization_code&redirect_uri=https://www.iocoder.cn&code=189956c07a174588a97157eabef2f93a @@ -31,7 +27,6 @@ grant_type=authorization_code&redirect_uri=https://www.iocoder.cn&code=189956c07 POST {{baseUrl}}/system/oauth2/token Content-Type: application/x-www-form-urlencoded Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== -tenant-id: {{adminTenantId}} grant_type=password&username=admin&password=admin123&scope=user.read @@ -39,7 +34,6 @@ grant_type=password&username=admin&password=admin123&scope=user.read POST {{baseUrl}}/system/oauth2/token Content-Type: application/x-www-form-urlencoded Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== -tenant-id: {{adminTenantId}} grant_type=client_credentials&scope=user.read @@ -47,16 +41,13 @@ grant_type=client_credentials&scope=user.read POST {{baseUrl}}/system/oauth2/token Content-Type: application/x-www-form-urlencoded Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== -tenant-id: {{adminTenantId}} grant_type=refresh_token&refresh_token=00895465d6994f72a9d926ceeed0f588 ### 请求 /system/oauth2/token + DELETE 接口 => 成功 DELETE {{baseUrl}}/system/oauth2/token?token=ca8a188f464441d6949c51493a2b7596 Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== -tenant-id: {{adminTenantId}} ### 请求 /system/oauth2/check-token 接口 => 成功 POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106 Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== -tenant-id: {{adminTenantId}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2UserController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2UserController.http index 4450ea9..60c705d 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2UserController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2UserController.http @@ -1,13 +1,11 @@ ### 请求 /system/oauth2/user/get 接口 => 成功 GET {{baseUrl}}/system/oauth2/user/get Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d -tenant-id: {{adminTenantId}} ### 请求 /system/oauth2/user/update 接口 => 成功 PUT {{baseUrl}}/system/oauth2/user/update Content-Type: application/json Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d -tenant-id: {{adminTenantId}} { "nickname": "灿能源码" diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java index 4fa774d..1826613 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java @@ -8,33 +8,32 @@ import lombok.NoArgsConstructor; import java.util.List; -@Schema(description = "管理后台 - 【开放接口】校验令牌 Response VO") +@Schema(description = "Admin API - open check token response") @Data @NoArgsConstructor @AllArgsConstructor public class OAuth2OpenCheckTokenRespVO { - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "666") + @Schema(description = "User id", requiredMode = Schema.RequiredMode.REQUIRED, example = "666") @JsonProperty("user_id") private Long userId; - @Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + + @Schema(description = "User type", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") @JsonProperty("user_type") private Integer userType; - @Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @JsonProperty("tenant_id") - private Long tenantId; - @Schema(description = "客户端编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "car") + @Schema(description = "Client id", requiredMode = Schema.RequiredMode.REQUIRED, example = "car") @JsonProperty("client_id") private String clientId; - @Schema(description = "授权范围", requiredMode = Schema.RequiredMode.REQUIRED, example = "user_info") + + @Schema(description = "Scopes", requiredMode = Schema.RequiredMode.REQUIRED, example = "user_info") private List scopes; - @Schema(description = "访问令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "tudou") + @Schema(description = "Access token", requiredMode = Schema.RequiredMode.REQUIRED, example = "tudou") @JsonProperty("access_token") private String accessToken; - @Schema(description = "过期时间,时间戳 / 1000,即单位:秒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1593092157") + @Schema(description = "Expire timestamp in seconds", requiredMode = Schema.RequiredMode.REQUIRED, example = "1593092157") private Long exp; } diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.http index fbaff1e..c073f7b 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.http @@ -1,4 +1,3 @@ ### 请求 /menu/list 接口 => 成功 GET {{baseUrl}}/system/menu/list Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.java index c238e1b..138590f 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.java @@ -16,7 +16,14 @@ import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import java.util.Comparator; import java.util.List; @@ -50,7 +57,7 @@ public class MenuController { @DeleteMapping("/delete") @Operation(summary = "删除菜单") - @Parameter(name = "id", description = "菜单编号", required= true, example = "1024") + @Parameter(name = "id", description = "菜单编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('system:menu:delete')") public CommonResult deleteMenu(@RequestParam("id") Long id) { menuService.deleteMenu(id); @@ -76,11 +83,9 @@ public class MenuController { } @GetMapping({"/list-all-simple", "simple-list"}) - @Operation(summary = "获取菜单精简信息列表", - description = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。在多租户的场景下,会只返回租户所在套餐有的菜单") + @Operation(summary = "获取菜单精简信息列表", description = "只包含已启用的菜单,用于【角色分配菜单】功能的选项") public CommonResult> getSimpleMenuList() { - List list = menuService.getMenuListByTenant( - new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); + List list = menuService.getMenuList(new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); list = menuService.filterDisableMenus(list); list.sort(Comparator.comparing(MenuDO::getSort)); return success(BeanUtils.toBean(list, MenuSimpleRespVO.class)); diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/RoleController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/RoleController.http index 375180a..1ab4a75 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/RoleController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/RoleController.http @@ -2,7 +2,6 @@ POST {{baseUrl}}/system/role/create Authorization: Bearer {{token}} Content-Type: application/json -tenant-id: {{adminTenantId}} { "name": "测试角色", @@ -14,7 +13,6 @@ tenant-id: {{adminTenantId}} POST {{baseUrl}}/system/role/update Authorization: Bearer {{token}} Content-Type: application/json -tenant-id: {{adminTenantId}} { "id": 100, @@ -26,7 +24,6 @@ tenant-id: {{adminTenantId}} POST {{baseUrl}}/system/role/delete Content-Type: application/x-www-form-urlencoded Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} roleId=14 @@ -34,9 +31,7 @@ roleId=14 GET {{baseUrl}}/system/role/get?id=100 Content-Type: application/x-www-form-urlencoded Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} ### /role/page 成功 GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10 Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/redis/RedisController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/redis/RedisController.http index 6eca16e..1af3452 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/redis/RedisController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/redis/RedisController.http @@ -1,5 +1,4 @@ ### 请求 /system/redis/get-monitor-info 接口 => 成功 GET {{baseUrl}}/system/redis/get-monitor-info Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserController.http index 0ca6915..1175c77 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserController.http @@ -2,10 +2,7 @@ GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10 Authorization: Bearer {{token}} #Authorization: Bearer test100 -tenant-id: {{adminTenantId}} ### 请求 /system/user/page 接口(测试访问别的租户) GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10 Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} -visit-tenant-id: 122 \ No newline at end of file diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.http index c94c2ad..fe4dd7c 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.http +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.http @@ -1,4 +1,3 @@ ### 请求 /system/user/profile/get 接口 => 没有权限 GET {{baseUrl}}/system/user/profile/get Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/permission/MenuService.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/permission/MenuService.java index 729a784..6aa438c 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/permission/MenuService.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/permission/MenuService.java @@ -7,96 +7,26 @@ import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO; import java.util.Collection; import java.util.List; -/** - * 菜单 Service 接口 - * - * @author hongawen - */ public interface MenuService { - /** - * 创建菜单 - * - * @param createReqVO 菜单信息 - * @return 创建出来的菜单编号 - */ Long createMenu(MenuSaveVO createReqVO); - /** - * 更新菜单 - * - * @param updateReqVO 菜单信息 - */ void updateMenu(MenuSaveVO updateReqVO); - /** - * 删除菜单 - * - * @param id 菜单编号 - */ void deleteMenu(Long id); - /** - * 批量删除菜单 - * - * @param ids 菜单编号数组 - */ void deleteMenuList(List ids); - /** - * 获得所有菜单列表 - * - * @return 菜单列表 - */ List getMenuList(); - /** - * 基于租户,筛选菜单列表 - * 注意,如果是系统租户,返回的还是全菜单 - * - * @param reqVO 筛选条件请求 VO - * @return 菜单列表 - */ - List getMenuListByTenant(MenuListReqVO reqVO); - - /** - * 过滤掉关闭的菜单及其子菜单 - * - * @param list 菜单列表 - * @return 过滤后的菜单列表 - */ List filterDisableMenus(List list); - /** - * 筛选菜单列表 - * - * @param reqVO 筛选条件请求 VO - * @return 菜单列表 - */ List getMenuList(MenuListReqVO reqVO); - /** - * 获得权限对应的菜单编号数组 - * - * @param permission 权限标识 - * @return 数组 - */ List getMenuIdListByPermissionFromCache(String permission); - /** - * 获得菜单 - * - * @param id 菜单编号 - * @return 菜单 - */ MenuDO getMenu(Long id); - /** - * 获得菜单数组 - * - * @param ids 菜单编号数组 - * @return 菜单数组 - */ List getMenuList(Collection ids); } diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/permission/MenuServiceImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/permission/MenuServiceImpl.java index e8825a1..dd98085 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/permission/MenuServiceImpl.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/permission/MenuServiceImpl.java @@ -125,13 +125,6 @@ public class MenuServiceImpl implements MenuService { return menuMapper.selectList(); } - @Override - public List getMenuListByTenant(MenuListReqVO reqVO) { - // 查询所有菜单,并过滤掉关闭的节点 - List menus = getMenuList(reqVO); - return menus; - } - @Override public List filterDisableMenus(List menuList) { if (CollUtil.isEmpty(menuList)){