From 69a679ff77b73340840994612bf396ef05c42953 Mon Sep 17 00:00:00 2001 From: gaoqr <13665037151@163.com> Date: Thu, 13 Jun 2024 16:38:17 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E8=8F=9C=E5=8D=95=E6=9D=83=E9=99=90?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D=EF=BC=9B=202=E3=80=81?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BF=AE=E6=94=B9=E8=8F=9C=E5=8D=95=E6=9D=83?= =?UTF-8?q?=E9=99=90=E5=88=B7=E6=96=B0=E6=9C=AC=E5=9C=B0=E7=BC=93=E5=AD=98?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/util/cache/CacheUtils.java | 14 +++++++-- .../core/security/OrganSecurityWebFilter.java | 13 +------- ...nfengRedisMQConsumerAutoConfiguration.java | 21 +++++++++++++ .../AbstractRedisSimpleMessageListener.java | 22 +++++++++++++ .../service/SecurityFrameworkService.java | 7 +++++ .../service/SecurityFrameworkServiceImpl.java | 9 ++++-- .../RedisRefreshChannelTopicConstants.java | 15 +++++++++ .../SystemPremissionRefreshRedisListener.java | 31 +++++++++++++++++++ .../oauth2/OAuth2TokenServiceImpl.java | 6 ++-- .../permission/PermissionServiceImpl.java | 19 ++++++++++-- 10 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/core/pubsub/AbstractRedisSimpleMessageListener.java create mode 100644 cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/RedisRefreshChannelTopicConstants.java create mode 100644 cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/listener/SystemPremissionRefreshRedisListener.java diff --git a/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/util/cache/CacheUtils.java b/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/util/cache/CacheUtils.java index 7a180960f..40351bd65 100644 --- a/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/util/cache/CacheUtils.java +++ b/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/util/cache/CacheUtils.java @@ -17,13 +17,21 @@ import java.util.concurrent.Executors; public class CacheUtils { public static LoadingCache buildAsyncReloadingCache(Duration duration, CacheLoader loader) { - Executor executor = Executors.newCachedThreadPool( // TODO 晨丰:可能要思考下,未来要不要做成可配置 - TtlExecutors.getDefaultDisableInheritableThreadFactory()); // TTL 保证 ThreadLocal 可以透传 return CacheBuilder.newBuilder() // 只阻塞当前数据加载线程,其他线程返回旧值 .refreshAfterWrite(duration) // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程 - .build(CacheLoader.asyncReloading(loader, executor)); + .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿:可能要思考下,未来要不要做成可配置 } + /** + * 构建同步刷新的 LoadingCache 对象 + * + * @param duration 过期时间 + * @param loader CacheLoader 对象 + * @return LoadingCache 对象 + */ + public static LoadingCache buildCache(Duration duration, CacheLoader loader) { + return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader); + } } diff --git a/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/security/OrganSecurityWebFilter.java b/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/security/OrganSecurityWebFilter.java index fc54c41d5..64fd51034 100644 --- a/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/security/OrganSecurityWebFilter.java +++ b/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/security/OrganSecurityWebFilter.java @@ -1,8 +1,6 @@ package com.cf.imes.framework.organ.core.security; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; -import com.cf.imes.framework.common.enums.RpcConstants; import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; import com.cf.imes.framework.common.pojo.CommonResult; import com.cf.imes.framework.common.util.servlet.ServletUtils; @@ -56,18 +54,10 @@ public class OrganSecurityWebFilter extends ApiRequestFilter { this.organFrameworkService = organFrameworkService; } - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - return super.shouldNotFilter(request) && - !StrUtil.startWithAny(request.getRequestURI(), RpcConstants.RPC_API_PREFIX); // 因为 RPC API 也会透传组织编号 - } - @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { Long organId = WebFrameworkUtils.getOrganId(request); - //Long organId = OrganContextHolder.getOrganId(); - boolean isRpcRequest = WebFrameworkUtils.isRpcRequest(request); // 1. 登陆的用户,校验是否有权限访问该组织,避免越权问题。 LoginUser user = SecurityFrameworkUtils.getLoginUser(); if (user != null) { @@ -76,8 +66,7 @@ public class OrganSecurityWebFilter extends ApiRequestFilter { organId = user.getOrganId(); OrganContextHolder.setOrganId(organId); // 如果传递了组织编号,则进行比对组织编号,避免越权问题 - } else if (!Objects.equals(user.getOrganId(), OrganContextHolder.getOrganId()) - && !isRpcRequest) { // Cloud 特殊逻辑:如果是 RPC 请求,就不校验了。主要考虑,一些场景下,会调用 OrganUtils 去切换组织 + } else if (!Objects.equals(user.getOrganId(), OrganContextHolder.getOrganId())) { // Cloud 特殊逻辑:如果是 RPC 请求,就不校验了。主要考虑,一些场景下,会调用 OrganUtils 去切换组织 log.error("[doFilterInternal][组织({}) User({}/{}) 越权访问组织({}) URL({}/{})]", user.getOrganId(), user.getId(), user.getUserType(), OrganContextHolder.getOrganId(), request.getRequestURI(), request.getMethod()); diff --git a/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/config/ChenfengRedisMQConsumerAutoConfiguration.java b/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/config/ChenfengRedisMQConsumerAutoConfiguration.java index e6d7d5229..9e4239c29 100644 --- a/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/config/ChenfengRedisMQConsumerAutoConfiguration.java +++ b/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/config/ChenfengRedisMQConsumerAutoConfiguration.java @@ -7,6 +7,7 @@ import com.cf.imes.framework.common.enums.DocumentEnum; import com.cf.imes.framework.mq.redis.core.RedisMQTemplate; import com.cf.imes.framework.mq.redis.core.job.RedisPendingMessageResendJob; import com.cf.imes.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener; +import com.cf.imes.framework.mq.redis.core.pubsub.AbstractRedisSimpleMessageListener; import com.cf.imes.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; import com.cf.imes.framework.redis.config.ChenfengRedisAutoConfiguration; import lombok.extern.slf4j.Slf4j; @@ -61,6 +62,26 @@ public class ChenfengRedisMQConsumerAutoConfiguration { return container; } + /** + * 创建 Redis Pub/Sub 广播消费的容器 + */ + @Bean + @ConditionalOnBean(AbstractRedisSimpleMessageListener.class) + public RedisMessageListenerContainer redisDelListenerContainer( + RedisTemplate redisTemplate, List listeners) { + // 创建 RedisMessageListenerContainer 对象 + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + // 设置 RedisConnection 工厂。 + container.setConnectionFactory(redisTemplate.getRequiredConnectionFactory()); + // 添加监听器 + listeners.forEach(listener -> { + container.addMessageListener(listener, listener.getTopic()); + log.info("[redisMessageListenerContainer][注册 ChannelTopic({}) 对应的监听器({})]", + listener.getTopic(), listener.getClass().getName()); + }); + return container; + } + /** * 创建 Redis Stream 重新消费的任务 */ diff --git a/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/core/pubsub/AbstractRedisSimpleMessageListener.java b/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/core/pubsub/AbstractRedisSimpleMessageListener.java new file mode 100644 index 000000000..1f03d3d46 --- /dev/null +++ b/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/core/pubsub/AbstractRedisSimpleMessageListener.java @@ -0,0 +1,22 @@ +package com.cf.imes.framework.mq.redis.core.pubsub; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.listener.ChannelTopic; + +/** + * Redis监听器 + * + * + * @author 晨丰科技 + */ +public abstract class AbstractRedisSimpleMessageListener implements MessageListener { + + /** + * 通道主题 + */ + @Getter + @Setter + private ChannelTopic topic; +} diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkService.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkService.java index 7072b583b..13b1613cb 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkService.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkService.java @@ -23,6 +23,13 @@ public interface SecurityFrameworkService { */ boolean hasAnyPermissions(String... permissions); + /** + * 清空本地缓存 + * + * @param + */ + void invalidateAll(); + /** * 判断是否有角色 * diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkServiceImpl.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkServiceImpl.java index c438c9efe..ec0885c00 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkServiceImpl.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkServiceImpl.java @@ -28,7 +28,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { /** * 针对 {@link #hasAnyRoles(String...)} 的缓存 */ - private final LoadingCache>, Boolean> hasAnyRolesCache = CacheUtils.buildAsyncReloadingCache( + private final LoadingCache>, Boolean> hasAnyRolesCache = CacheUtils.buildCache( Duration.ofMinutes(1L), // 过期时间 1 分钟 new CacheLoader>, Boolean>() { @@ -42,7 +42,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { /** * 针对 {@link #hasAnyPermissions(String...)} 的缓存 */ - private final LoadingCache>, Boolean> hasAnyPermissionsCache = CacheUtils.buildAsyncReloadingCache( + private final LoadingCache>, Boolean> hasAnyPermissionsCache = CacheUtils.buildCache( Duration.ofMinutes(1L), // 过期时间 1 分钟 new CacheLoader>, Boolean>() { @@ -65,6 +65,11 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { return hasAnyPermissionsCache.get(new KeyValue<>(SecurityFrameworkUtils.getLoginUserId(), Arrays.asList(permissions))); } + @Override + public void invalidateAll() { + hasAnyPermissionsCache.invalidateAll(); + } + @Override public boolean hasRole(String role) { return hasAnyRoles(role); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/RedisRefreshChannelTopicConstants.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/RedisRefreshChannelTopicConstants.java new file mode 100644 index 000000000..89b2dd743 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/RedisRefreshChannelTopicConstants.java @@ -0,0 +1,15 @@ +package com.cf.imes.module.system.dal.redis; + +/** + * system redis监听刷新通道名称 常量 + * + * @author Gqr + * @since 2024/6/13 14:55 + */ +public interface RedisRefreshChannelTopicConstants { + + /** + * 刷新权限 + */ + String PREMISSION_REFRESH = "PREMISSION_REFRESH"; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/listener/SystemPremissionRefreshRedisListener.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/listener/SystemPremissionRefreshRedisListener.java new file mode 100644 index 000000000..a16cb6023 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/listener/SystemPremissionRefreshRedisListener.java @@ -0,0 +1,31 @@ +package com.cf.imes.module.system.dal.redis.listener; + +import com.cf.imes.framework.mq.redis.core.pubsub.AbstractRedisSimpleMessageListener; +import com.cf.imes.framework.security.core.service.SecurityFrameworkService; +import com.cf.imes.module.system.dal.redis.RedisRefreshChannelTopicConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.stereotype.Component; + +/** + * system PREMISSION_REFRESH redis监听器 + * + * @author Gqr + * @since 2024/6/13 10:32 + */ +@Component +public class SystemPremissionRefreshRedisListener extends AbstractRedisSimpleMessageListener { + + @Autowired + private SecurityFrameworkService securityFrameworkService; + + public SystemPremissionRefreshRedisListener() { + super.setTopic(new ChannelTopic(RedisRefreshChannelTopicConstants.PREMISSION_REFRESH)); + } + + @Override + public void onMessage(Message message, byte[] bytes) { + securityFrameworkService.invalidateAll(); + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java index e2fa178e3..82f71e54f 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -159,8 +159,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { .setClientId(clientDO.getClientId()).setScopes(scopes) .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds())) .setLarge(large).setDbNo(dbNo).setTableNo(tableNo).setDataCode(dataCode) - .setOrganId(organId).setNickname(nickname) - ; + .setOrganId(organId).setNickname(nickname); + if (organId != null) { + OrganContextHolder.setOrganId(organId); + } boolean b = securityFrameworkService.hasAnyRoles(userId,"super_admin"); refreshToken.setIsSupAdmin(b); oauth2RefreshTokenMapper.insert(refreshToken); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java index 13713855c..f58486c80 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java @@ -4,14 +4,12 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.extra.spring.SpringUtil; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.util.collection.CollectionUtils; import com.cf.imes.framework.datapermission.core.annotation.DataPermission; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.framework.organ.core.aop.OrganIgnore; import com.cf.imes.framework.organ.core.context.OrganContextHolder; -import com.cf.imes.framework.organ.core.db.OrganBaseDO; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; import com.cf.imes.module.system.api.permission.dto.DeptDataPermissionRespDTO; import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO; @@ -22,6 +20,7 @@ import com.cf.imes.module.system.dal.dataobject.permission.UserRoleDO; import com.cf.imes.module.system.dal.mysql.permission.RoleMenuMapper; import com.cf.imes.module.system.dal.mysql.permission.UserRoleMapper; import com.cf.imes.module.system.dal.redis.RedisKeyConstants; +import com.cf.imes.module.system.dal.redis.RedisRefreshChannelTopicConstants; import com.cf.imes.module.system.enums.permission.DataScopeEnum; import com.cf.imes.module.system.service.dept.DeptService; import com.cf.imes.module.system.service.user.AdminUserService; @@ -30,14 +29,17 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Suppliers; import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -72,6 +74,9 @@ public class PermissionServiceImpl implements PermissionService { @Resource private AdminUserService userService; + @Autowired + private RedisTemplate redisTemplate; + @Override public boolean hasAnyPermissions(Long userId, String... permissions) { // 如果为空,说明已经有权限 @@ -179,6 +184,16 @@ public class PermissionServiceImpl implements PermissionService { if (CollUtil.isNotEmpty(deleteMenuIds)) { roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); } + + // 角色菜单权限发生修改,通知刷新本地缓存 + if (CollectionUtil.isNotEmpty(createMenuIds) || CollectionUtil.isNotEmpty(deleteMenuIds)) { + CompletableFuture.runAsync(() -> { + redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PREMISSION_REFRESH, ""); + }).exceptionally(e -> { + log.error("redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PREMISSION_REFRESH, e.getMessage()); + return null; + }); + } } @Override