forked from github/cool-admin-java
fix: RBAC模型P0+P1安全修复
P0-1: 中间表更新添加@Transactional事务保护 P0-2: 角色分配增加越权提权校验 P0-3: 未配置权限接口改为默认拒绝策略 P1-4: 实现relevance字段递归扩展子部门 P1-5: move/info接口增加部门权限校验 P1-6: 权限映射缓存变更时自动刷新 P1-7: 租户ID仅从JWT获取,角色/部门实体加租户隔离
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<ConfigAttribute> configAttributes)
|
||||
throws AccessDeniedException, InsufficientAuthenticationException {
|
||||
if (configAttributes == null) {
|
||||
return;
|
||||
}
|
||||
List<String> 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<ConfigAttribute> 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;
|
||||
|
||||
@@ -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<BaseSysDepartmentEntity> {
|
||||
public class BaseSysDepartmentEntity extends TenantEntity<BaseSysDepartmentEntity> {
|
||||
@ColumnDefine(comment = "部门名称", notNull = true)
|
||||
private String name;
|
||||
|
||||
|
||||
@@ -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<BaseSysRoleEntity> {
|
||||
public class BaseSysRoleEntity extends TenantEntity<BaseSysRoleEntity> {
|
||||
|
||||
@Index
|
||||
@ColumnDefine(comment = "用户ID", notNull = true, type = "bigint")
|
||||
|
||||
@@ -47,6 +47,13 @@ public class MySecurityMetadataSource implements FilterInvocationSecurityMetadat
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除权限映射缓存,下次请求时重新加载
|
||||
*/
|
||||
public void clearCache() {
|
||||
map = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定用户请求的url是否在权限表中 如果在权限表中,则返回给decide方法,用来判定用户是否有此权限 如果不在权限表中则放行
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<Long> deptIds = baseSysRoleDepartmentMapper
|
||||
.selectListByQuery(queryWrapper)
|
||||
.stream().map(BaseSysRoleDepartmentEntity::getDepartmentId).toArray(Long[]::new);
|
||||
.stream().map(BaseSysRoleDepartmentEntity::getDepartmentId).collect(java.util.stream.Collectors.toList());
|
||||
Set<Long> 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<Long> result) {
|
||||
List<BaseSysDepartmentEntity> 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<BaseSysUserRoleEntity> 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<BaseSysUserEntity> 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<BaseSysUserRoleEntity> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BaseSysUserMapper, B
|
||||
|
||||
final private BaseSysUserRoleMapper baseSysUserRoleMapper;
|
||||
|
||||
final private BaseSysRoleMapper baseSysRoleMapper;
|
||||
|
||||
@Override
|
||||
public Object page(JSONObject requestParams, Page<BaseSysUserEntity> page, QueryWrapper qw) {
|
||||
String keyWord = requestParams.getStr("keyWord");
|
||||
@@ -137,6 +142,7 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl<BaseSysUserMapper, B
|
||||
|
||||
@Override
|
||||
public void move(Long departmentId, Long[] userIds) {
|
||||
CoolPreconditions.check(!baseSysPermsService.hasDepartmentPermission(departmentId), "无权将用户移动到该部门");
|
||||
UpdateChain.of(BaseSysUserEntity.class)
|
||||
.set(BaseSysUserEntity::getDepartmentId, departmentId)
|
||||
.in(BaseSysUserEntity::getId, (Object) userIds).update();
|
||||
@@ -177,24 +183,46 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl<BaseSysUserMapper, B
|
||||
public void modifyAfter(JSONObject requestParams, BaseSysUserEntity baseSysUserEntity,
|
||||
ModifyEnum type) {
|
||||
if (type != ModifyEnum.DELETE && requestParams.get("roleIdList", Long[].class) != null) {
|
||||
// 刷新权限
|
||||
baseSysPermsService.updateUserRole(baseSysUserEntity.getId(),
|
||||
requestParams.get("roleIdList", Long[].class));
|
||||
Long[] roleIdList = requestParams.get("roleIdList", Long[].class);
|
||||
validateRoleAssignment(roleIdList);
|
||||
baseSysPermsService.updateUserRole(baseSysUserEntity.getId(), roleIdList);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRoleAssignment(Long[] roleIdList) {
|
||||
if (ArrayUtil.isEmpty(roleIdList)) {
|
||||
return;
|
||||
}
|
||||
if (CoolSecurityUtil.isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
List<Long> requestedRoles = CollUtil.toList(roleIdList);
|
||||
CoolPreconditions.check(requestedRoles.contains(1L), "无权分配超级管理员角色");
|
||||
Long[] currentRoleIds = baseSysPermsService.getRoles(CoolSecurityUtil.getCurrentUserId());
|
||||
if (ObjectUtil.isEmpty(currentRoleIds)) {
|
||||
CoolPreconditions.check(true, "无权分配任何角色");
|
||||
}
|
||||
List<Long> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user