Browse Source

fix: 代码审查修复 — 安全问题 + 规范 + 空路由

- Spec#1 CRITICAL: 移除 /api/resources/** 白名单,改为 @PreAuthorize(hasRole('ADMIN'))
- Spec#2 WARNING: applyToEntity 空串路由不覆盖 DB path (null→isNotBlank)
- Standards#1: validateHierarchy 复用 MenuType.isLegalChild,消除重复代码
- Standards#2: 消除魔法数字 3→MenuType.BUTTON.getCode(),消除重复 switch
master
luoweijian 1 month ago
parent
commit
a70ce78ddd
  1. 2
      crm-auth/src/main/java/com/crm/auth/config/SecurityConfig.java
  2. 2
      crm-auth/src/main/java/com/crm/auth/controller/ResourceController.java
  3. 5
      crm-auth/src/main/java/com/crm/auth/service/impl/PermissionServiceImpl.java
  4. 25
      crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java

2
crm-auth/src/main/java/com/crm/auth/config/SecurityConfig.java

@ -29,8 +29,6 @@ public class SecurityConfig {
private static final String[] DEFAULT_IGNORE_URLS = { private static final String[] DEFAULT_IGNORE_URLS = {
// 登录接口 // 登录接口
"/api/auth/login/**", "/api/auth/login/**",
// 资源树管理接口(ADR-0011)
"/api/resources/**",
// 接口文档 // 接口文档
"/doc.html", "/doc.html",
"/webjars/**", "/webjars/**",

2
crm-auth/src/main/java/com/crm/auth/controller/ResourceController.java

@ -5,6 +5,7 @@ import com.crm.auth.service.IResourceService;
import com.crm.base.domain.result.Result; import com.crm.base.domain.result.Result;
import com.crm.file.domain.dto.FileInfoDTO; import com.crm.file.domain.dto.FileInfoDTO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -16,6 +17,7 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/api/resources") @RequestMapping("/api/resources")
@RequiredArgsConstructor @RequiredArgsConstructor
@PreAuthorize("hasRole('ADMIN')")
public class ResourceController { public class ResourceController {
private final IResourceService resourceService; private final IResourceService resourceService;

5
crm-auth/src/main/java/com/crm/auth/service/impl/PermissionServiceImpl.java

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.crm.auth.domain.entity.*; import com.crm.auth.domain.entity.*;
import com.crm.auth.domain.enums.MenuType;
import com.crm.auth.mapper.*; import com.crm.auth.mapper.*;
import com.crm.auth.service.DeptTreeCache; import com.crm.auth.service.DeptTreeCache;
import com.crm.base.security.DataScopeLevel; import com.crm.base.security.DataScopeLevel;
@ -83,7 +84,7 @@ public class PermissionServiceImpl {
.map(SysRoleMenu::getMenuId).collect(Collectors.toSet()); .map(SysRoleMenu::getMenuId).collect(Collectors.toSet());
List<SysMenu> menus = sysMenuMapper.selectBatchIds(menuIds); List<SysMenu> menus = sysMenuMapper.selectBatchIds(menuIds);
menus.stream() menus.stream()
.filter(m -> m.getMenuType() != null && m.getMenuType() == 3) .filter(m -> m.getMenuType() != null && m.getMenuType() == MenuType.BUTTON.getCode())
.filter(m -> "enabled".equals(m.getStatus())) .filter(m -> "enabled".equals(m.getStatus()))
.map(SysMenu::getPerms) .map(SysMenu::getPerms)
.filter(StrUtil::isNotBlank) .filter(StrUtil::isNotBlank)
@ -114,7 +115,7 @@ public class PermissionServiceImpl {
List<SysMenu> allMenus = sysMenuMapper.selectBatchIds(menuIds); List<SysMenu> allMenus = sysMenuMapper.selectBatchIds(menuIds);
List<SysMenu> visibleMenus = allMenus.stream() List<SysMenu> visibleMenus = allMenus.stream()
.filter(m -> Boolean.TRUE.equals(m.getVisible())) .filter(m -> Boolean.TRUE.equals(m.getVisible()))
.filter(m -> m.getMenuType() != 3) .filter(m -> m.getMenuType() != MenuType.BUTTON.getCode())
.sorted(Comparator.comparingInt(m -> m.getSort() != null ? m.getSort() : 0)) .sorted(Comparator.comparingInt(m -> m.getSort() != null ? m.getSort() : 0))
.collect(Collectors.toList()); .collect(Collectors.toList());
return TreeUtils.buildTree(visibleMenus, SysMenu::getId, SysMenu::getParentId, SysMenu::setChildren); return TreeUtils.buildTree(visibleMenus, SysMenu::getId, SysMenu::getParentId, SysMenu::setChildren);

25
crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java

@ -162,12 +162,11 @@ public class ResourceServiceImpl implements IResourceService {
// ==================== 校验逻辑 ==================== // ==================== 校验逻辑 ====================
/** 层级约束:6 种非法父子组合 */ /** 层级约束:复用 {@link MenuType#isLegalChild},根节点单独处理 */
private void validateHierarchy(ResourceNode node) { private void validateHierarchy(ResourceNode node) {
MenuType childType = node.getType(); MenuType childType = node.getType();
if (node.getParentId() == null || node.getParentId() == 0) { if (node.getParentId() == null || node.getParentId() == 0) {
// 根节点:只允许 catalog / menu
if (childType == MenuType.BUTTON) { if (childType == MenuType.BUTTON) {
throw new BusinessErrorException(CODE_RESOURCE_INVALID, "根节点不允许添加 button 类型"); throw new BusinessErrorException(CODE_RESOURCE_INVALID, "根节点不允许添加 button 类型");
} }
@ -180,17 +179,13 @@ public class ResourceServiceImpl implements IResourceService {
} }
MenuType parentType = MenuType.fromCode(parent.getMenuType()); MenuType parentType = MenuType.fromCode(parent.getMenuType());
// 叶子节点 if (!MenuType.isLegalChild(parentType, childType)) {
if (parentType == MenuType.BUTTON) { String msg = switch (parentType) {
throw new BusinessErrorException(CODE_RESOURCE_INVALID, "button 是叶子节点,不可添加子节点"); case CATALOG -> "catalog 节点下只能添加 menu 类型";
} case MENU -> "menu 节点下只能添加 button 类型";
// catalog 只能挂 menu case BUTTON -> "button 是叶子节点,不可添加子节点";
if (parentType == MenuType.CATALOG && childType != MenuType.MENU) { };
throw new BusinessErrorException(CODE_RESOURCE_INVALID, "catalog 节点下只能添加 menu 类型"); throw new BusinessErrorException(CODE_RESOURCE_INVALID, msg);
}
// menu 只能挂 button
if (parentType == MenuType.MENU && childType != MenuType.BUTTON) {
throw new BusinessErrorException(CODE_RESOURCE_INVALID, "menu 节点下只能添加 button 类型");
} }
} }
@ -233,8 +228,8 @@ public class ResourceServiceImpl implements IResourceService {
entity.setMenuType(node.getType().getCode()); entity.setMenuType(node.getType().getCode());
} }
// route / path:若未传则保留原有值 // route / path:非空值覆盖,空串/空白保留原有值
if (node.getRoute() != null) { if (StrUtil.isNotBlank(node.getRoute())) {
entity.setPath(node.getRoute()); entity.setPath(node.getRoute());
} }

Loading…
Cancel
Save