From d26cd851aaa5079593ebcc6c926de1a39c2651e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=A4=A9=E5=90=91=E4=B8=8A?= Date: Fri, 25 Nov 2022 15:34:15 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BD=91=E5=85=B3?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E9=89=B4=E6=9D=83=E5=8A=9F=E8=83=BD=E3=80=81?= =?UTF-8?q?=E5=BE=AE=E6=9C=8D=E5=8A=A1=E6=B3=A8=E8=A7=A3=E9=89=B4=E6=9D=83?= =?UTF-8?q?=E5=BC=80=E5=85=B3=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/constant/SecurityConstants.java | 15 +++ .../security/aspect/PreAuthorizeAspect.java | 13 ++- .../config/PathPermissionMappingConfig.java | 93 +++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../com/ruoyi/gateway/filter/AuthFilter.java | 99 ++++++++++++++++--- 5 files changed, 204 insertions(+), 17 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/SecurityConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/SecurityConstants.java index d02baeb0..fbbcca64 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/SecurityConstants.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/SecurityConstants.java @@ -46,4 +46,19 @@ public class SecurityConstants * 角色权限 */ public static final String ROLE_PERMISSION = "role_permission"; + + /** + * 缓存在redis中的控制器路径与权限字符串对应的hash键 + */ + public static final String PATH_PERMISSION_MAP = "path_permission_map"; + + /** + * 缓存的角色名前缀 + */ + public static final String ROLE_PREFIX = "ROLE_"; + + /** + * 匿名角色(公共权限的角色名字) + */ + public static final String ROLE_ANON = "ROLE_ANON"; } diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/PreAuthorizeAspect.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/PreAuthorizeAspect.java index 7877820b..52d937c3 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/PreAuthorizeAspect.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/PreAuthorizeAspect.java @@ -1,16 +1,18 @@ package com.ruoyi.common.security.aspect; -import java.lang.reflect.Method; +import com.ruoyi.common.security.annotation.RequiresLogin; +import com.ruoyi.common.security.annotation.RequiresPermissions; +import com.ruoyi.common.security.annotation.RequiresRoles; +import com.ruoyi.common.security.auth.AuthUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; -import com.ruoyi.common.security.annotation.RequiresLogin; -import com.ruoyi.common.security.annotation.RequiresPermissions; -import com.ruoyi.common.security.annotation.RequiresRoles; -import com.ruoyi.common.security.auth.AuthUtil; + +import java.lang.reflect.Method; /** * 基于 Spring Aop 的注解鉴权 @@ -19,6 +21,7 @@ import com.ruoyi.common.security.auth.AuthUtil; */ @Aspect @Component +@ConditionalOnProperty(prefix = "security.aspect", name = "enabled", havingValue = "true", matchIfMissing = true) public class PreAuthorizeAspect { /** diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java new file mode 100644 index 00000000..ad318f36 --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java @@ -0,0 +1,93 @@ +package com.ruoyi.common.security.config; + +import com.ruoyi.common.core.constant.SecurityConstants; +import com.ruoyi.common.core.utils.SpringUtils; +import com.ruoyi.common.redis.service.RedisService; +import com.ruoyi.common.security.annotation.RequiresPermissions; +import com.ruoyi.common.security.annotation.RequiresRoles; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import javax.annotation.PostConstruct; +import java.util.*; +import java.util.stream.Collectors; + + +/** + * 缓存所有api,方便网关鉴权 + */ +@ConditionalOnProperty(prefix = "security.gateway", name = "enabled", havingValue = "true") +public class PathPermissionMappingConfig { + @Value("${routePrefix}") + private String routePrefix; + + @PostConstruct + public PathPermissionMappingConfig execute() { + RedisService redisService = SpringUtils.getBean(RedisService.class); + RequestMappingHandlerMapping bean = SpringUtils.getBean("requestMappingHandlerMapping"); + Map handlerMethods = bean.getHandlerMethods(); + /** + * 路径->权限字符串映射,例如 /user/list_GET->system:user:list + */ + Map pathPermsMap = new TreeMap<>(); + + handlerMethods.forEach((k, v) -> { + RequiresRoles requiresRoles = v.getMethodAnnotation(RequiresRoles.class); + RequiresPermissions requiresPermissions = v.getMethodAnnotation(RequiresPermissions.class); + + Set methods = k.getMethodsCondition().getMethods(); + Set patternValues = k.getPatternValues(); + /** + * @RequestMapping注解 + */ + if(methods.isEmpty()) { + methods = new HashSet<>(); + methods.addAll(Arrays.asList(RequestMethod.GET, RequestMethod.POST)); + } + + if(requiresPermissions == null && requiresRoles == null) { + addPathPermsMap(SecurityConstants.ROLE_ANON, pathPermsMap, methods, patternValues); + } + if(requiresPermissions != null) { + for (String perms : requiresPermissions.value()) { + addPathPermsMap(perms, pathPermsMap, methods, patternValues); + } + } + if(requiresRoles != null) { + for (String role : requiresRoles.value()) { + addPathPermsMap(SecurityConstants.ROLE_PREFIX+ role, pathPermsMap, methods, patternValues); + } + } + }); + System.out.println("pathPermsMap = " + pathPermsMap); + redisService.setCacheMap(SecurityConstants.PATH_PERMISSION_MAP, pathPermsMap); + return this; + } + + /** + * 一个path对应多个perms + * @param perms + * @param pathPermsMap + * @param methods + * @param patternValues + */ + private void addPathPermsMap(String perms, Map pathPermsMap, Set methods, Set patternValues) { + for (RequestMethod method : methods) { + for (String patternValue : patternValues) { + String key = routePrefix + patternValue + "_" + method.name(); + pathPermsMap.put(key, perms); + } + } + } + + public static void main(String[] args) { + String[] arr = new String[] {"a", "b"}; + Set set = Arrays.stream(arr).collect(Collectors.toSet()); + System.out.println("set = " + set); + } + +} diff --git a/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 38915f2f..bdb5465c 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -3,3 +3,4 @@ com.ruoyi.common.security.service.TokenService com.ruoyi.common.security.aspect.PreAuthorizeAspect com.ruoyi.common.security.aspect.InnerAuthAspect com.ruoyi.common.security.handler.GlobalExceptionHandler +com.ruoyi.common.security.config.PathPermissionMappingConfig diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java index 101de638..65cf539d 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java @@ -1,14 +1,6 @@ package com.ruoyi.gateway.filter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.core.Ordered; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; +import com.alibaba.fastjson2.JSONObject; import com.ruoyi.common.core.constant.CacheConstants; import com.ruoyi.common.core.constant.HttpStatus; import com.ruoyi.common.core.constant.SecurityConstants; @@ -19,8 +11,24 @@ import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.gateway.config.properties.IgnoreWhiteProperties; import io.jsonwebtoken.Claims; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import javax.annotation.Resource; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + /** * 网关鉴权 * @@ -35,9 +43,13 @@ public class AuthFilter implements GlobalFilter, Ordered @Autowired private IgnoreWhiteProperties ignoreWhite; - @Autowired + @Resource private RedisService redisService; + @Value("${security.gateway.enabled:false}") + private boolean gatewayAuth; + + private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) @@ -73,16 +85,79 @@ public class AuthFilter implements GlobalFilter, Ordered { return unauthorizedResponse(exchange, "令牌验证失败"); } - // 设置用户信息到请求 addHeader(mutate, SecurityConstants.USER_KEY, userkey); addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid); addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username); // 内部请求来源参数清除 removeHeader(mutate, SecurityConstants.FROM_SOURCE); + // 通过网关鉴权 + if(gatewayAuth) { + // admin不需要鉴权 + if(isAdmin(userid)) { + return chain.filter(exchange.mutate().request(mutate.build()).build()); + } + // 网关验证权限 + String api = url + "_" + request.getMethod().name(); + if(!hasPermission(api, userkey)) { + log.warn("无权访问:{}", api); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "无权访问", HttpStatus.FORBIDDEN); + } + } return chain.filter(exchange.mutate().request(mutate.build()).build()); } + private boolean isAdmin(String userid) { + return "1".equals(userid); + } + + private boolean hasPermission(String api, String token) { + // 使用JSONObject接收,避免导入依赖 + JSONObject loginUser = redisService.getCacheObject(CacheConstants.LOGIN_TOKEN_KEY + token); + // 获取登录用户的资源列表 + Set permissions = (Set) loginUser.get("permissions"); + // 获取登录用的角色列表 + Set roles = (Set) loginUser.get("roles"); + // 获取系统所有控制器路径与权限对应的map + Map pathPermsMap = redisService.getCacheMap(SecurityConstants.PATH_PERMISSION_MAP); + + Set matchedPerms = pathPermsMap.entrySet().stream() + .filter(entry -> match(entry.getKey(), api)) + .map(entry -> entry.getValue()) + .collect(Collectors.toSet()); + if(!matchedPerms.isEmpty()) { + // 所有角色权限 + Set rolePerms = matchedPerms.stream().filter(item -> item.startsWith("ROLE_")).collect(Collectors.toSet()); + // 所有资源权限 + matchedPerms.removeAll(rolePerms); + + if(!rolePerms.isEmpty()) { + if(rolePerms.contains(SecurityConstants.ROLE_ANON)) { + log.info("允许访问公共权限:{},{}", api, rolePerms); + return true; + } + rolePerms = rolePerms.stream().map(item -> item.substring(SecurityConstants.ROLE_PREFIX.length())).collect(Collectors.toSet()); + // 求交集 + rolePerms.retainAll(roles); + if(!rolePerms.isEmpty()) { + log.info("允许访问角色权限:{}, {}", api, rolePerms); + return true; + } + } + // 求交集 + matchedPerms.retainAll(permissions); + if(!matchedPerms.isEmpty()) { + log.info("允许访问资源权限:{},{}", api, matchedPerms); + return true; + } + } + log.info("没有找到匹配的权限:{}, {}", api, matchedPerms); + return false; + } + + private boolean match(String pattern, String api) { + return antPathMatcher.match(pattern, api); + } private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) { if (value == null) @@ -101,7 +176,7 @@ public class AuthFilter implements GlobalFilter, Ordered private Mono unauthorizedResponse(ServerWebExchange exchange, String msg) { - log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); + log.error("[鉴权异常处理]请求路径:{}, {}", exchange.getRequest().getPath(), msg); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED); } From 47993ef57391f4b0fa9f266655deb431eecf7e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=A4=A9=E5=90=91=E4=B8=8A?= Date: Tue, 29 Nov 2022 16:02:27 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BD=91=E5=85=B3?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E9=89=B4=E6=9D=83=E5=8A=9F=E8=83=BD=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/PathPermissionMappingConfig.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java index ad318f36..2a2a479a 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java @@ -14,11 +14,18 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl import javax.annotation.PostConstruct; import java.util.*; -import java.util.stream.Collectors; /** - * 缓存所有api,方便网关鉴权 + * =====================================网关鉴权使用说明======================================= + * 场景: + * 微服务部署在内网,确定安全,无需在每个微服务都实现一次权限控制的逻辑,可以在网关层面实现统一鉴权 + * 使用方式: + * 1、在每个微服务的配置文件中添加参数:security.aspect.enabled: false 关闭系统默认的通过注解方式鉴权,默认开启 + * 2、在每个微服务的配置文件中添加参数:routePrefix: 值为网关中微服务匹配的路由地址,例如: /auth + * 3、在网关配置文件中添加参数:security.gateway.enabled: true 启用网关统一鉴权,默认关闭 + * + * 通过反射扫描所有控制器,缓存所有控制器的映射路径以及对应的权限注解,缓存到redis,方便网关鉴权 */ @ConditionalOnProperty(prefix = "security.gateway", name = "enabled", havingValue = "true") public class PathPermissionMappingConfig { @@ -59,7 +66,7 @@ public class PathPermissionMappingConfig { } if(requiresRoles != null) { for (String role : requiresRoles.value()) { - addPathPermsMap(SecurityConstants.ROLE_PREFIX+ role, pathPermsMap, methods, patternValues); + addPathPermsMap(SecurityConstants.ROLE_PREFIX + role, pathPermsMap, methods, patternValues); } } }); @@ -84,10 +91,4 @@ public class PathPermissionMappingConfig { } } - public static void main(String[] args) { - String[] arr = new String[] {"a", "b"}; - Set set = Arrays.stream(arr).collect(Collectors.toSet()); - System.out.println("set = " + set); - } - } From e36b3642dad804a148c66a3500ca9a9969ae6bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=A4=A9=E5=90=91=E4=B8=8A?= Date: Tue, 29 Nov 2022 17:16:14 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E7=9A=84=E5=90=8D=E5=AD=97=E5=92=8C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=BE=93=E5=87=BA=E7=BA=A7=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/aspect/PreAuthorizeAspect.java | 2 +- .../config/PathPermissionMappingConfig.java | 17 ++++++++++------- .../com/ruoyi/gateway/filter/AuthFilter.java | 6 +++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/PreAuthorizeAspect.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/PreAuthorizeAspect.java index 52d937c3..1c3397c5 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/PreAuthorizeAspect.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/PreAuthorizeAspect.java @@ -21,7 +21,7 @@ import java.lang.reflect.Method; */ @Aspect @Component -@ConditionalOnProperty(prefix = "security.aspect", name = "enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(prefix = "security.annotation", name = "enabled", havingValue = "true", matchIfMissing = true) public class PreAuthorizeAspect { /** diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java index 2a2a479a..7afd7523 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java @@ -19,18 +19,21 @@ import java.util.*; /** * =====================================网关鉴权使用说明======================================= * 场景: - * 微服务部署在内网,确定安全,无需在每个微服务都实现一次权限控制的逻辑,可以在网关层面实现统一鉴权 + * 微服务部署在内网,确定安全,无需在每个微服务都实现鉴权的逻辑,可以在网关层面实现统一鉴权 * 使用方式: - * 1、在每个微服务的配置文件中添加参数:security.aspect.enabled: false 关闭系统默认的通过注解方式鉴权,默认开启 - * 2、在每个微服务的配置文件中添加参数:routePrefix: 值为网关中微服务匹配的路由地址,例如: /auth + * 1、在每个微服务的配置文件中添加参数:security.annotation.enabled: false 关闭系统默认的通过注解方式鉴权,默认开启 + * 2、在每个微服务的配置文件中添加参数:pathPrefix: 值为网关中微服务匹配的路由地址前缀,例如: /auth * 3、在网关配置文件中添加参数:security.gateway.enabled: true 启用网关统一鉴权,默认关闭 * * 通过反射扫描所有控制器,缓存所有控制器的映射路径以及对应的权限注解,缓存到redis,方便网关鉴权 */ -@ConditionalOnProperty(prefix = "security.gateway", name = "enabled", havingValue = "true") +@ConditionalOnProperty(prefix = "security.annotation", name = "enabled", havingValue = "false") public class PathPermissionMappingConfig { - @Value("${routePrefix}") - private String routePrefix; + /** + * 微服务在网关配置中predicates中的Path前缀,例如: /system + */ + @Value("${pathPrefix}") + private String pathPrefix; @PostConstruct public PathPermissionMappingConfig execute() { @@ -85,7 +88,7 @@ public class PathPermissionMappingConfig { private void addPathPermsMap(String perms, Map pathPermsMap, Set methods, Set patternValues) { for (RequestMethod method : methods) { for (String patternValue : patternValues) { - String key = routePrefix + patternValue + "_" + method.name(); + String key = pathPrefix + patternValue + "_" + method.name(); pathPermsMap.put(key, perms); } } diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java index 65cf539d..d2540622 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java @@ -133,21 +133,21 @@ public class AuthFilter implements GlobalFilter, Ordered if(!rolePerms.isEmpty()) { if(rolePerms.contains(SecurityConstants.ROLE_ANON)) { - log.info("允许访问公共权限:{},{}", api, rolePerms); + log.debug("允许访问公共权限:{},{}", api, rolePerms); return true; } rolePerms = rolePerms.stream().map(item -> item.substring(SecurityConstants.ROLE_PREFIX.length())).collect(Collectors.toSet()); // 求交集 rolePerms.retainAll(roles); if(!rolePerms.isEmpty()) { - log.info("允许访问角色权限:{}, {}", api, rolePerms); + log.debug("允许访问角色权限:{}, {}", api, rolePerms); return true; } } // 求交集 matchedPerms.retainAll(permissions); if(!matchedPerms.isEmpty()) { - log.info("允许访问资源权限:{},{}", api, matchedPerms); + log.debug("允许访问资源权限:{},{}", api, matchedPerms); return true; } } From a5d5787735605f2c89b1ed7671178bf05ed06d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=A4=A9=E5=90=91=E4=B8=8A?= Date: Wed, 30 Nov 2022 08:40:31 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E4=BF=AE=E6=94=B9'{'=E5=AF=B9=E9=BD=90?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/PathPermissionMappingConfig.java | 47 ++++++++++++------- .../com/ruoyi/gateway/filter/AuthFilter.java | 36 +++++++++----- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java index 7afd7523..afee49ae 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/config/PathPermissionMappingConfig.java @@ -19,16 +19,17 @@ import java.util.*; /** * =====================================网关鉴权使用说明======================================= * 场景: - * 微服务部署在内网,确定安全,无需在每个微服务都实现鉴权的逻辑,可以在网关层面实现统一鉴权 + * 微服务部署在内网,确定安全,无需在每个微服务都实现鉴权的逻辑,可以在网关层面实现统一鉴权 * 使用方式: - * 1、在每个微服务的配置文件中添加参数:security.annotation.enabled: false 关闭系统默认的通过注解方式鉴权,默认开启 - * 2、在每个微服务的配置文件中添加参数:pathPrefix: 值为网关中微服务匹配的路由地址前缀,例如: /auth - * 3、在网关配置文件中添加参数:security.gateway.enabled: true 启用网关统一鉴权,默认关闭 - * + * 1、在每个微服务的配置文件中添加参数:security.annotation.enabled: false 关闭系统默认的通过注解方式鉴权,默认开启 + * 2、在每个微服务的配置文件中添加参数:pathPrefix: 值为网关中微服务匹配的路由地址前缀,例如: /auth + * 3、在网关配置文件中添加参数:security.gateway.enabled: true 启用网关统一鉴权,默认关闭 + *

* 通过反射扫描所有控制器,缓存所有控制器的映射路径以及对应的权限注解,缓存到redis,方便网关鉴权 */ @ConditionalOnProperty(prefix = "security.annotation", name = "enabled", havingValue = "false") -public class PathPermissionMappingConfig { +public class PathPermissionMappingConfig +{ /** * 微服务在网关配置中predicates中的Path前缀,例如: /system */ @@ -36,7 +37,8 @@ public class PathPermissionMappingConfig { private String pathPrefix; @PostConstruct - public PathPermissionMappingConfig execute() { + public PathPermissionMappingConfig execute() + { RedisService redisService = SpringUtils.getBean(RedisService.class); RequestMappingHandlerMapping bean = SpringUtils.getBean("requestMappingHandlerMapping"); Map handlerMethods = bean.getHandlerMethods(); @@ -45,7 +47,8 @@ public class PathPermissionMappingConfig { */ Map pathPermsMap = new TreeMap<>(); - handlerMethods.forEach((k, v) -> { + handlerMethods.forEach((k, v) -> + { RequiresRoles requiresRoles = v.getMethodAnnotation(RequiresRoles.class); RequiresPermissions requiresPermissions = v.getMethodAnnotation(RequiresPermissions.class); @@ -54,21 +57,27 @@ public class PathPermissionMappingConfig { /** * @RequestMapping注解 */ - if(methods.isEmpty()) { + if (methods.isEmpty()) + { methods = new HashSet<>(); methods.addAll(Arrays.asList(RequestMethod.GET, RequestMethod.POST)); } - if(requiresPermissions == null && requiresRoles == null) { + if (requiresPermissions == null && requiresRoles == null) + { addPathPermsMap(SecurityConstants.ROLE_ANON, pathPermsMap, methods, patternValues); } - if(requiresPermissions != null) { - for (String perms : requiresPermissions.value()) { + if (requiresPermissions != null) + { + for (String perms : requiresPermissions.value()) + { addPathPermsMap(perms, pathPermsMap, methods, patternValues); } } - if(requiresRoles != null) { - for (String role : requiresRoles.value()) { + if (requiresRoles != null) + { + for (String role : requiresRoles.value()) + { addPathPermsMap(SecurityConstants.ROLE_PREFIX + role, pathPermsMap, methods, patternValues); } } @@ -80,14 +89,18 @@ public class PathPermissionMappingConfig { /** * 一个path对应多个perms + * * @param perms * @param pathPermsMap * @param methods * @param patternValues */ - private void addPathPermsMap(String perms, Map pathPermsMap, Set methods, Set patternValues) { - for (RequestMethod method : methods) { - for (String patternValue : patternValues) { + private void addPathPermsMap(String perms, Map pathPermsMap, Set methods, Set patternValues) + { + for (RequestMethod method : methods) + { + for (String patternValue : patternValues) + { String key = pathPrefix + patternValue + "_" + method.name(); pathPermsMap.put(key, perms); } diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java index d2540622..01e534c5 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java @@ -31,7 +31,7 @@ import java.util.stream.Collectors; /** * 网关鉴权 - * + * * @author ruoyi */ @Component @@ -92,14 +92,17 @@ public class AuthFilter implements GlobalFilter, Ordered // 内部请求来源参数清除 removeHeader(mutate, SecurityConstants.FROM_SOURCE); // 通过网关鉴权 - if(gatewayAuth) { + if (gatewayAuth) + { // admin不需要鉴权 - if(isAdmin(userid)) { + if (isAdmin(userid)) + { return chain.filter(exchange.mutate().request(mutate.build()).build()); } // 网关验证权限 String api = url + "_" + request.getMethod().name(); - if(!hasPermission(api, userkey)) { + if (!hasPermission(api, userkey)) + { log.warn("无权访问:{}", api); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "无权访问", HttpStatus.FORBIDDEN); } @@ -107,11 +110,13 @@ public class AuthFilter implements GlobalFilter, Ordered return chain.filter(exchange.mutate().request(mutate.build()).build()); } - private boolean isAdmin(String userid) { + private boolean isAdmin(String userid) + { return "1".equals(userid); } - private boolean hasPermission(String api, String token) { + private boolean hasPermission(String api, String token) + { // 使用JSONObject接收,避免导入依赖 JSONObject loginUser = redisService.getCacheObject(CacheConstants.LOGIN_TOKEN_KEY + token); // 获取登录用户的资源列表 @@ -125,28 +130,33 @@ public class AuthFilter implements GlobalFilter, Ordered .filter(entry -> match(entry.getKey(), api)) .map(entry -> entry.getValue()) .collect(Collectors.toSet()); - if(!matchedPerms.isEmpty()) { + if (!matchedPerms.isEmpty()) + { // 所有角色权限 Set rolePerms = matchedPerms.stream().filter(item -> item.startsWith("ROLE_")).collect(Collectors.toSet()); // 所有资源权限 matchedPerms.removeAll(rolePerms); - if(!rolePerms.isEmpty()) { - if(rolePerms.contains(SecurityConstants.ROLE_ANON)) { + if (!rolePerms.isEmpty()) + { + if (rolePerms.contains(SecurityConstants.ROLE_ANON)) + { log.debug("允许访问公共权限:{},{}", api, rolePerms); return true; } rolePerms = rolePerms.stream().map(item -> item.substring(SecurityConstants.ROLE_PREFIX.length())).collect(Collectors.toSet()); // 求交集 rolePerms.retainAll(roles); - if(!rolePerms.isEmpty()) { + if (!rolePerms.isEmpty()) + { log.debug("允许访问角色权限:{}, {}", api, rolePerms); return true; } } // 求交集 matchedPerms.retainAll(permissions); - if(!matchedPerms.isEmpty()) { + if (!matchedPerms.isEmpty()) + { log.debug("允许访问资源权限:{},{}", api, matchedPerms); return true; } @@ -155,9 +165,11 @@ public class AuthFilter implements GlobalFilter, Ordered return false; } - private boolean match(String pattern, String api) { + private boolean match(String pattern, String api) + { return antPathMatcher.match(pattern, api); } + private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) { if (value == null)