From e02c7a02dc436d27265b99bd70dbc6cb996e9520 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Fri, 17 Jul 2026 18:59:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20RBAC=E6=A8=A1=E5=9E=8BP0+P1=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0-1: 中间表更新添加@Transactional事务保护 P0-2: 角色分配增加越权提权校验 P0-3: 未配置权限接口改为默认拒绝策略 P1-4: 实现relevance字段递归扩展子部门 P1-5: move/info接口增加部门权限校验 P1-6: 权限映射缓存变更时自动刷新 P1-7: 租户ID仅从JWT获取,角色/部门实体加租户隔离 --- .../core/request/RequestParamsFilter.java | 2 +- .../security/MyAccessDecisionManager.java | 25 +++++++-- .../entity/sys/BaseSysDepartmentEntity.java | 4 +- .../base/entity/sys/BaseSysRoleEntity.java | 4 +- .../security/MySecurityMetadataSource.java | 7 +++ .../base/service/sys/BaseSysPermsService.java | 14 +++++ .../sys/impl/BaseSysPermsServiceImpl.java | 54 +++++++++++++++++-- .../sys/impl/BaseSysUserServiceImpl.java | 42 ++++++++++++--- 8 files changed, 132 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/cool/core/request/RequestParamsFilter.java b/src/main/java/com/cool/core/request/RequestParamsFilter.java index cbcc551..89ae06b 100644 --- a/src/main/java/com/cool/core/request/RequestParamsFilter.java +++ b/src/main/java/com/cool/core/request/RequestParamsFilter.java @@ -37,7 +37,7 @@ public class RequestParamsFilter implements Filter { JSONObject requestParams = new JSONObject(); String language = request.getHeader("language"); String coolEid = request.getHeader("cool-admin-eid"); - Long tenantId = StrUtil.isEmpty(coolEid) ? null : Long.parseLong(coolEid); + Long tenantId = null; if (StrUtil.isNotEmpty(request.getContentType()) && request.getContentType().contains("multipart/form-data")) { servletRequest.setAttribute("requestParams", requestParams); servletRequest.setAttribute("cool-language", language); diff --git a/src/main/java/com/cool/core/security/MyAccessDecisionManager.java b/src/main/java/com/cool/core/security/MyAccessDecisionManager.java index 3c410af..dddde58 100644 --- a/src/main/java/com/cool/core/security/MyAccessDecisionManager.java +++ b/src/main/java/com/cool/core/security/MyAccessDecisionManager.java @@ -1,5 +1,8 @@ package com.cool.core.security; +import cn.hutool.core.util.ObjectUtil; +import com.cool.core.enums.UserTypeEnum; +import com.cool.core.security.jwt.JwtUser; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -27,20 +30,22 @@ public class MyAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object o, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { - if (configAttributes == null) { - return; - } List urls = ignoredUrlsProperties.getAdminAuthUrls(); String url = ((FilterInvocation) o).getRequestUrl().split("[?]")[0]; if (urls.contains(url)) { return; } + if (configAttributes == null) { + if (isSuperAdmin(authentication)) { + return; + } + throw new AccessDeniedException("抱歉,您没有访问权限"); + } Iterator iterator = configAttributes.iterator(); while (iterator.hasNext()) { ConfigAttribute c = iterator.next(); String needPerm = c.getAttribute(); for (GrantedAuthority ga : authentication.getAuthorities()) { - // 匹配用户拥有的ga 和 系统中的needPerm if (needPerm.trim().equals(ga.getAuthority())) { return; } @@ -49,6 +54,18 @@ public class MyAccessDecisionManager implements AccessDecisionManager { throw new AccessDeniedException("抱歉,您没有访问权限"); } + private boolean isSuperAdmin(Authentication authentication) { + Object principal = authentication.getPrincipal(); + if (principal instanceof JwtUser) { + JwtUser jwtUser = (JwtUser) principal; + if (ObjectUtil.equal(jwtUser.getUserTypeEnum(), UserTypeEnum.APP)) { + return false; + } + return "admin".equals(jwtUser.getUsername()); + } + return false; + } + @Override public boolean supports(ConfigAttribute configAttribute) { return true; diff --git a/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java index 02bf5c0..ab4fa6b 100644 --- a/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java +++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysDepartmentEntity.java @@ -1,6 +1,6 @@ package com.cool.modules.base.entity.sys; -import com.cool.core.base.BaseEntity; +import com.cool.core.base.TenantEntity; import com.mybatisflex.annotation.Column; import com.cool.core.annotation.ColumnDefine; @@ -14,7 +14,7 @@ import lombok.Setter; @Getter @Setter @Table(value = "base_sys_department", comment = "系统部门") -public class BaseSysDepartmentEntity extends BaseEntity { +public class BaseSysDepartmentEntity extends TenantEntity { @ColumnDefine(comment = "部门名称", notNull = true) private String name; diff --git a/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java index e49cb04..e2a690e 100644 --- a/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java +++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java @@ -1,6 +1,6 @@ package com.cool.modules.base.entity.sys; -import com.cool.core.base.BaseEntity; +import com.cool.core.base.TenantEntity; import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Table; import com.cool.core.mybatis.handler.Fastjson2TypeHandler; @@ -14,7 +14,7 @@ import org.dromara.autotable.annotation.enums.IndexTypeEnum; @Getter @Setter @Table(value = "base_sys_role", comment = "系统角色表") -public class BaseSysRoleEntity extends BaseEntity { +public class BaseSysRoleEntity extends TenantEntity { @Index @ColumnDefine(comment = "用户ID", notNull = true, type = "bigint") diff --git a/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java b/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java index 2f97be4..ef7a1a3 100644 --- a/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java +++ b/src/main/java/com/cool/modules/base/security/MySecurityMetadataSource.java @@ -47,6 +47,13 @@ public class MySecurityMetadataSource implements FilterInvocationSecurityMetadat } } + /** + * 清除权限映射缓存,下次请求时重新加载 + */ + public void clearCache() { + map = null; + } + /** * 判定用户请求的url是否在权限表中 如果在权限表中,则返回给decide方法,用来判定用户是否有此权限 如果不在权限表中则放行 * diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java index e9cd9c2..36a1e99 100644 --- a/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java +++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java @@ -165,4 +165,18 @@ public interface BaseSysPermsService { */ void refreshPermsByRoleId(Long roleId); + /** + * 校验当前用户是否有权访问指定部门的数据 + * + * @param departmentId 部门ID + * @return true=有权访问 + */ + boolean hasDepartmentPermission(Long departmentId); + + /** + * 获取当前用户可访问的部门ID集合,超管返回null表示全部 + * + * @return 部门ID数组,超管返回null + */ + Long[] getCurrentUserDepartmentIds(); } diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java index 041f44d..7c169f3 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java @@ -15,15 +15,18 @@ import com.cool.core.util.CoolSecurityUtil; import com.cool.core.util.SpringContextUtils; import com.cool.modules.base.entity.sys.*; import com.cool.modules.base.mapper.sys.*; +import com.cool.modules.base.security.MySecurityMetadataSource; import com.cool.modules.base.service.sys.BaseSysPermsService; import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.row.Row; import java.util.*; import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -74,9 +77,30 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { if (!CollUtil.toList(roleIds).contains(1L)) { queryWrapper.in(BaseSysRoleDepartmentEntity::getRoleId, (Object) roleIds); } - return baseSysRoleDepartmentMapper + List deptIds = baseSysRoleDepartmentMapper .selectListByQuery(queryWrapper) - .stream().map(BaseSysRoleDepartmentEntity::getDepartmentId).toArray(Long[]::new); + .stream().map(BaseSysRoleDepartmentEntity::getDepartmentId).collect(java.util.stream.Collectors.toList()); + Set expandedDeptIds = new LinkedHashSet<>(deptIds); + for (Long roleId : roleIds) { + if (roleId == 1L) continue; + BaseSysRoleEntity role = baseSysRoleMapper.selectOneById(roleId); + if (role != null && role.getRelevance() != null && role.getRelevance() == 1) { + for (Long deptId : deptIds) { + collectChildDeptIds(deptId, expandedDeptIds); + } + } + } + return expandedDeptIds.toArray(Long[]::new); + } + + private void collectChildDeptIds(Long parentId, Set result) { + List children = baseSysDepartmentMapper + .selectListByQuery(QueryWrapper.create().eq(BaseSysDepartmentEntity::getParentId, parentId)); + for (BaseSysDepartmentEntity child : children) { + if (result.add(child.getId())) { + collectChildDeptIds(child.getId(), result); + } + } } @Override @@ -187,6 +211,7 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { } @Override + @Transactional(rollbackFor = Exception.class) public void updatePerms(Long roleId, Long[] menuIdList, Long[] departmentIds) { // 更新菜单权限 baseSysRoleMenuMapper.deleteByQuery(QueryWrapper.create().eq(BaseSysRoleMenuEntity::getRoleId, roleId)); @@ -213,6 +238,7 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { if (ObjectUtil.isNotEmpty(batchRoleDepartmentList)) { baseSysRoleDepartmentMapper.insertBatch(batchRoleDepartmentList); } + SpringContextUtils.getBean(MySecurityMetadataSource.class).clearCache(); cachedThreadPool.submit(() -> { // 刷新对应角色用户的权限 List userRoles = baseSysUserRoleMapper @@ -224,6 +250,7 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { } @Override + @Transactional(rollbackFor = Exception.class) public void updateUserRole(Long userId, Long[] roleIdList) { baseSysUserRoleMapper.deleteByQuery(QueryWrapper.create().eq(BaseSysUserRoleEntity::getUserId, userId)); if (roleIdList == null) { @@ -252,7 +279,7 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { @Async @Override public void refreshPermsByMenuId(Long menuId) { - // 刷新超管权限、 找出这个菜单的所有用户、 刷新用户权限 + SpringContextUtils.getBean(MySecurityMetadataSource.class).clearCache(); List superAdmins = baseSysUserMapper .selectListByQuery(QueryWrapper.create() .eq(BaseSysUserEntity::getUsername, "admin")); @@ -269,11 +296,30 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { @Override public void refreshPermsByRoleId(Long roleId) { - // 找出角色对应的所有用户 List list = baseSysUserRoleMapper .selectListByQuery(QueryWrapper.create().eq(BaseSysUserRoleEntity::getRoleId, roleId)); list.forEach(e -> { refreshPerms(e.getUserId()); }); } + + @Override + public boolean hasDepartmentPermission(Long departmentId) { + if (CoolSecurityUtil.isSuperAdmin()) { + return true; + } + Long[] permittedIds = getDepartmentIdsByUserId(CoolSecurityUtil.getCurrentUserId()); + if (ObjectUtil.isEmpty(permittedIds)) { + return false; + } + return Arrays.asList(permittedIds).contains(departmentId); + } + + @Override + public Long[] getCurrentUserDepartmentIds() { + if (CoolSecurityUtil.isSuperAdmin()) { + return null; + } + return getDepartmentIdsByUserId(CoolSecurityUtil.getCurrentUserId()); + } } diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java index 7ddd0b4..0d57738 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java @@ -10,6 +10,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.MD5; import cn.hutool.json.JSONObject; @@ -20,9 +21,11 @@ import com.cool.core.exception.CoolPreconditions; import com.cool.core.util.CoolSecurityUtil; import com.cool.core.util.DatabaseDialectUtils; import com.cool.modules.base.entity.sys.BaseSysDepartmentEntity; +import com.cool.modules.base.entity.sys.BaseSysRoleEntity; import com.cool.modules.base.entity.sys.BaseSysUserEntity; import com.cool.modules.base.entity.sys.BaseSysUserRoleEntity; import com.cool.modules.base.mapper.sys.BaseSysDepartmentMapper; +import com.cool.modules.base.mapper.sys.BaseSysRoleMapper; import com.cool.modules.base.mapper.sys.BaseSysUserMapper; import com.cool.modules.base.mapper.sys.BaseSysUserRoleMapper; import com.cool.modules.base.service.sys.BaseSysPermsService; @@ -52,6 +55,8 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl page, QueryWrapper qw) { String keyWord = requestParams.getStr("keyWord"); @@ -137,6 +142,7 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl requestedRoles = CollUtil.toList(roleIdList); + CoolPreconditions.check(requestedRoles.contains(1L), "无权分配超级管理员角色"); + Long[] currentRoleIds = baseSysPermsService.getRoles(CoolSecurityUtil.getCurrentUserId()); + if (ObjectUtil.isEmpty(currentRoleIds)) { + CoolPreconditions.check(true, "无权分配任何角色"); + } + List allowedRoles = CollUtil.toList(currentRoleIds); + for (Long roleId : requestedRoles) { + BaseSysRoleEntity role = baseSysRoleMapper.selectOneById(roleId); + CoolPreconditions.checkEmpty(role, "角色不存在: {}", roleId); + CoolPreconditions.check(!allowedRoles.contains(roleId), "无权分配角色: {}", role.getName()); } } @Override public BaseSysUserEntity info(Long id) { BaseSysUserEntity userEntity = getById(id); + CoolPreconditions.checkEmpty(userEntity, "用户不存在"); + if (!CoolSecurityUtil.isSuperAdmin()) { + CoolPreconditions.check(!baseSysPermsService.hasDepartmentPermission(userEntity.getDepartmentId()), "无权查看该用户信息"); + } Long[] roleIdList = baseSysPermsService.getRoles(id); BaseSysDepartmentEntity departmentEntity = baseSysDepartmentMapper.selectOneById( userEntity.getDepartmentId()); userEntity.setPassword(null); - - userEntity.setRoleIdList(List.of(roleIdList)); - userEntity.setDepartmentName(departmentEntity != null ? departmentEntity.getName() : userEntity.getDepartmentName() ); - + userEntity.setDepartmentName(departmentEntity != null ? departmentEntity.getName() : userEntity.getDepartmentName()); return userEntity; } } \ No newline at end of file