1、菜单权限问题修复;

2、增加修改菜单权限刷新本地缓存;
This commit is contained in:
gaoqr
2024-06-13 16:38:17 +08:00
parent e8e2052be6
commit 69a679ff77
10 changed files with 136 additions and 21 deletions
@@ -17,13 +17,21 @@ import java.util.concurrent.Executors;
public class CacheUtils {
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> 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 <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader);
}
}
@@ -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());
@@ -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<AbstractRedisSimpleMessageListener> 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 重新消费的任务
*/
@@ -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;
}
@@ -23,6 +23,13 @@ public interface SecurityFrameworkService {
*/
boolean hasAnyPermissions(String... permissions);
/**
* 清空本地缓存
*
* @param
*/
void invalidateAll();
/**
* 判断是否有角色
*
@@ -28,7 +28,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
/**
* 针对 {@link #hasAnyRoles(String...)} 的缓存
*/
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyRolesCache = CacheUtils.buildAsyncReloadingCache(
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyRolesCache = CacheUtils.buildCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<KeyValue<Long, List<String>>, Boolean>() {
@@ -42,7 +42,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
/**
* 针对 {@link #hasAnyPermissions(String...)} 的缓存
*/
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyPermissionsCache = CacheUtils.buildAsyncReloadingCache(
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyPermissionsCache = CacheUtils.buildCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<KeyValue<Long, List<String>>, 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);
@@ -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";
}
@@ -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();
}
}
@@ -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);
@@ -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