You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

44 KiB

权限管理系统

**本文引用的文件** - [PermissionResolver.java](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java) - [PermissionGrant.java](file://crm-auth/src/main/java/com/crm/auth/security/PermissionGrant.java) - [PermissionResolverImpl.java](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java) - [JwtAuthenticationFilter.java](file://crm-auth/src/main/java/com/crm/auth/security/JwtAuthenticationFilter.java) - [SystemController.java](file://crm-auth/src/main/java/com/crm/auth/controller/SystemController.java) - [SysMenu.java](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java) - [ResourceNode.java](file://crm-auth/src/main/java/com/crm/auth/domain/dto/ResourceNode.java) - [MenuType.java](file://crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java) - [DataScopeEnum.java](file://crm-auth/src/main/java/com/crm/auth/domain/enums/DataScopeEnum.java) - [TreeUtils.java](file://crm-base/src/main/java/com/crm/base/utils/TreeUtils.java) - [DataInitializer.java](file://crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java) - [ApiPermissionInterceptor.java](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java) - [ApiPermissionCache.java](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java) - [ApiPermissionRule.java](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionRule.java) - [WebMvcConfig.java](file://crm-auth/src/main/java/com/crm/auth/config/WebMvcConfig.java) - [AuthServiceImpl.java](file://crm-auth/src/main/java/com/crm/auth/service/impl/AuthServiceImpl.java) - [ResourceServiceImpl.java](file://crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java)

更新摘要

所做更改

  • 新增 动态API权限拦截系统,基于Caffeine缓存和HandlerInterceptor实现URL级别的权限控制
  • 增强 AuthServiceImpl认证流程重构,优化事务处理和第三方登录逻辑
  • 完善 资源树变更时的缓存失效机制,确保权限规则实时更新
  • 扩展 测试覆盖范围,包含API权限拦截、认证流程和数据权限的完整测试场景

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能考虑
  8. 故障排查指南
  9. 结论
  10. 附录

简介

本文件为基于RBAC(角色-资源)的权限管理系统文档,覆盖角色管理、菜单权限控制与数据权限范围控制。重点说明实体设计(如 SysRole、SysMenu)、权限校验逻辑(PermissionResolver三组件架构)、数据权限类型(DataScopeEnum),以及权限注解使用、动态权限加载与权限缓存机制的实现细节和配置方法。读者可据此快速理解系统权限模型并正确扩展与使用。

更新 本次更新重点关注新增的动态API权限拦截系统和改进的认证流程重构,包括ApiPermissionInterceptor、ApiPermissionCache等核心组件,以及AuthServiceImpl的事务处理优化。

项目结构

本项目采用多模块组织:

  • crm-app:应用启动入口与全局配置
  • crm-auth:认证与权限核心模块,包含RBAC实体、服务、安全拦截、第三方登录等
  • crm-base:公共基础能力(异常、工具、通用配置、数据可见性上下文等)
  • crm-file:文件服务(与本权限文档无直接关联)
graph TB
subgraph "应用层"
APP["crm-app<br/>启动与应用配置"]
end
subgraph "认证与权限"
AUTH["crm-auth<br/>RBAC实体/服务/安全/客户端"]
end
subgraph "基础能力"
BASE["crm-base<br/>异常/工具/通用配置/数据可见性"]
end
APP --> AUTH
AUTH --> BASE

章节来源

  • CrmAppApplication.java:1-200
  • AuthApplication.java:1-200

核心组件

  • RBAC 实体
    • SysRole:角色定义,承载角色标识、名称、状态等元信息
    • SysMenu:增强的菜单与按钮资源定义,支持三层层级结构(目录-菜单-按钮)和细粒度权限控制
    • SysUserRole、SysRoleMenu:用户-角色、角色-菜单的多对多关系表
    • SysDept、SysUserDept:部门与用户部门关系,用于数据权限范围
  • 数据传输对象
    • ResourceNode:支持SysMenu与ResourceNode之间的双向转换,包含children字段支持层次结构
  • 权限解析引擎(重构
    • PermissionResolver接口:定义权限解析的核心抽象,包含resolve()和visibleMenuTree()两个方法
    • PermissionGrant值对象:不可变的权限解析结果,包含数据可见性、权限码并集和角色编码
    • PermissionResolverImpl实现:具体的权限解析逻辑实现,采用共享查询链优化
  • 动态API权限拦截系统(新增)
    • ApiPermissionInterceptor:HandlerInterceptor实现,基于AntPathMatcher进行URL模式匹配
    • ApiPermissionCache:基于Caffeine的进程内缓存,懒加载已注册的API权限规则
    • ApiPermissionRule:记录apiUrl pattern与perms权限码的映射关系
  • 控制器层
    • ResourceController:统一权限资源树管理端接口
    • SystemController:系统管理相关接口,集成新的权限解析器
  • 安全与拦截
    • SecurityConfig、JwtAuthenticationFilter:认证流程与JWT过滤,集成新的权限解析器
    • DataScopeInterceptor、DataScopeTables:数据权限拦截与SQL改写
    • AuthLoginUser、TokenService:当前登录用户上下文与令牌操作
  • 数据权限
    • DataScopeEnum:数据权限范围枚举(如全部、本部门、本部门及子部门、仅本人等)
    • DataScope(注解)、DataScopeHelper、DataVisibilityContext:注解驱动的数据可见性控制

更新 权限解析引擎已重构为三组件架构,PermissionResolver接口定义了清晰的权限解析契约,PermissionGrant提供了不可变的权限结果封装,PermissionResolverImpl实现了高效的权限解析逻辑。新增的动态API权限拦截系统通过ApiPermissionInterceptor和ApiPermissionCache实现了URL级别的权限控制。SystemController现在直接使用PermissionResolver进行菜单树获取,简化了调用方式。

章节来源

  • SysRole.java:1-200
  • SysMenu.java:12-76
  • ResourceNode.java:16-84
  • PermissionResolver.java:16-35
  • PermissionGrant.java:23-67
  • PermissionResolverImpl.java:42-139
  • ApiPermissionInterceptor.java:32-82
  • ApiPermissionCache.java:25-65
  • SystemController.java:36-44

架构总览

下图展示认证与权限在请求链路中的关键交互:从JWT鉴权到权限校验、再到数据权限注入与SQL改写,以及新增的动态API权限拦截。

sequenceDiagram
participant Client as "客户端"
participant Filter as "JwtAuthenticationFilter"
participant Interceptor as "ApiPermissionInterceptor"
participant Resolver as "PermissionResolver"
participant Grant as "PermissionGrant"
participant Controller as "SystemController"
participant DB as "数据库"
Client->>Filter : "HTTP 请求 (带JWT)"
Filter->>Filter : "验证Token"
Filter->>Interceptor : "API权限拦截检查"
Interceptor->>Cache : "获取API权限规则"
Cache-->>Interceptor : "返回规则列表"
Interceptor->>Interceptor : "AntPathMatcher匹配"
Interceptor-->>Filter : "权限检查通过"
Filter->>Resolver : "调用权限解析器"
Resolver->>DB : "查询用户角色和权限"
DB-->>Resolver : "返回权限数据"
Resolver-->>Filter : "返回PermissionGrant"
Filter->>Filter : "设置Security上下文"
Controller->>Resolver : "获取菜单树"
Resolver->>DB : "查询可见菜单"
DB-->>Resolver : "返回菜单数据"
Resolver-->>Controller : "返回菜单树"
Controller-->>Client : "响应菜单数据"

更新 新的架构中,JwtAuthenticationFilter直接使用PermissionResolver进行权限解析,通过PermissionGrant封装完整的权限信息。新增的ApiPermissionInterceptor在JWT过滤器之后执行,提供URL级别的权限控制。SystemController也直接使用PermissionResolver获取菜单树,简化了调用链路。

图表来源

  • SecurityConfig.java:1-200
  • JwtAuthenticationFilter.java:32-56
  • ApiPermissionInterceptor.java:38-80
  • PermissionResolver.java:16-35
  • SystemController.java:40-44

详细组件分析

权限解析三组件架构

重构 权限解析引擎已重构为清晰的三组件架构:

PermissionResolver接口

定义了权限解析的核心抽象:

  • resolve(Long userId):解析用户的数据可见性与全部授权码
  • visibleMenuTree(Long userId):获取用户可见的菜单树

PermissionGrant值对象

不可变的权限解析结果封装:

  • DataVisibility visibility:数据可见性信息
  • Set<String> permCodes:权限码并集(button节点且status=enabled)
  • Set<String> roleCodes:角色编码集合
  • asAuthorities():合成Spring Security authorities

PermissionResolverImpl实现

具体的权限解析逻辑实现:

  • 共享查询链:用户→角色→授权→资源
  • 多角色权限合并:取最宽档可见性
  • 权限码并集:仅包含button节点且启用状态的perms
  • 菜单树构建:过滤button节点并按sort排序
classDiagram
class PermissionResolver {
+resolve(userId) PermissionGrant
+visibleMenuTree(userId) SysMenu[]
}
class PermissionGrant {
+DataVisibility visibility
+Set~String~ permCodes
+Set~String~ roleCodes
+asAuthorities() SimpleGrantedAuthority[]
}
class PermissionResolverImpl {
-SysUserRoleMapper sysUserRoleMapper
-SysRoleMapper sysRoleMapper
-SysRoleMenuMapper sysRoleMenuMapper
-SysMenuMapper sysMenuMapper
-AuthUserMapper authUserMapper
-DeptTreeCache deptTreeCache
-SysUserDeptMapper sysUserDeptMapper
+resolve(userId) PermissionGrant
+visibleMenuTree(userId) SysMenu[]
}
PermissionResolver <|.. PermissionResolverImpl
PermissionResolverImpl --> PermissionGrant : 返回

更新 新的三组件架构提供了清晰的职责分离,PermissionResolver接口定义了明确的契约,PermissionGrant作为不可变值对象确保数据安全,PermissionResolverImpl专注于权限解析逻辑的实现。

图表来源

  • PermissionResolver.java:16-35
  • PermissionGrant.java:23-67
  • PermissionResolverImpl.java:42-139

动态API权限拦截系统(新增)

新增 动态API权限拦截系统基于HandlerInterceptor实现URL级别的权限控制:

ApiPermissionInterceptor

HandlerInterceptor实现,负责URL模式匹配和权限校验:

  • fail-open策略:未匹配任何注册规则时放行请求
  • AntPathMatcher匹配:支持*单层通配和**多层通配
  • 权限检查:从SecurityContext读取用户authority集合进行匹配
  • 403响应:匹配到规则但用户无对应权限时返回403

ApiPermissionCache

基于Caffeine的进程内缓存,懒加载API权限规则:

  • 单key缓存:存储整份规则列表,避免频繁重建
  • 懒加载机制:首次访问时从数据库加载已启用的button节点
  • 主动失效:资源树变更后调用invalidate()清空缓存
  • 规则过滤:仅加载menuType=BUTTON且status='enabled'且有apiUrl的节点

ApiPermissionRule

简单的record类,记录API权限规则映射:

  • pattern:Ant风格的URL模式
  • perms:对应的权限码
flowchart TD
Start(["HTTP请求进入"]) --> GetRules["从缓存获取规则"]
GetRules --> MatchCheck{"是否有匹配规则?"}
MatchCheck --> |否| Pass["放行请求"]
MatchCheck --> |是| CheckPerm["检查用户权限"]
CheckPerm --> HasPerm{"用户有权限?"}
HasPerm --> |是| Pass
HasPerm --> |否| Deny["返回403"]
Pass --> End(["继续处理"])
Deny --> End

更新 动态API权限拦截系统提供了灵活的URL级别权限控制,通过Caffeine缓存保证性能,fail-open策略确保向后兼容性。

图表来源

  • ApiPermissionInterceptor.java:38-80
  • ApiPermissionCache.java:37-63
  • ApiPermissionRule.java:9

WebMvcConfig拦截器注册

新增 WebMvcConfig配置类负责注册API权限拦截器:

  • 拦截器注册:将ApiPermissionInterceptor添加到MVC拦截器链
  • 路径匹配:仅拦截/api/**路径的请求
  • 执行顺序:在Spring Security(JWT过滤器)之后、Controller之前执行
flowchart TD
Start(["请求进入"]) --> JwtFilter["JWT过滤器"]
JwtFilter --> ApiInterceptor["API权限拦截器"]
ApiInterceptor --> Controller["业务控制器"]
Controller --> Response["返回响应"]

更新 拦截器的注册确保了权限检查的执行顺序,先进行JWT认证,再进行API权限检查,最后执行业务逻辑。

图表来源

  • WebMvcConfig.java:19-23

JwtAuthenticationFilter集成新架构

更新 JwtAuthenticationFilter已集成新的PermissionResolver:

  • fail-closed安全原则:权限解析失败即拒绝请求
  • 一次解析完成:通过PermissionResolver.resolve()一次性获取所有权限信息
  • 自动上下文装载:将DataVisibility装载到DataVisibilityContext
  • Spring Security集成:通过PermissionGrant.asAuthorities()生成authorities
flowchart TD
Start(["进入过滤器"]) --> TokenCheck{"Token有效?"}
TokenCheck --> |否| Next["继续过滤器链"]
TokenCheck --> |是| ResolvePerm["调用PermissionResolver.resolve()"]
ResolvePerm --> LoadContext["装载DataVisibility到上下文"]
LoadContext --> CreateAuth["创建UsernamePasswordAuthenticationToken"]
CreateAuth --> SetContext["设置到SecurityContextHolder"]
SetContext --> Next
Next --> End(["结束"])

更新 新的实现遵循fail-closed原则,权限解析失败时直接让请求失败,不存在部分成功的状态。通过PermissionGrant统一处理权限码和角色编码,简化了Spring Security的集成。

图表来源

  • JwtAuthenticationFilter.java:32-56

SystemController集成新架构

更新 SystemController已集成新的PermissionResolver:

  • 菜单树获取:通过permissionResolver.visibleMenuTree(userId)直接获取菜单树
  • 简化调用:无需复杂的Service层调用,直接通过接口获取数据
  • 类型安全:返回类型为List,保证类型安全
flowchart TD
Start(["菜单树请求"]) --> GetUserId["获取当前用户ID"]
GetUserId --> CallResolver["调用PermissionResolver.visibleMenuTree()"]
CallResolver --> QueryRoles["查询用户角色"]
QueryRoles --> QueryMenus["查询角色授权的菜单"]
QueryMenus --> FilterVisible["过滤可见菜单"]
FilterVisible --> BuildTree["构建菜单树"]
BuildTree --> Return["返回菜单树"]

更新 SystemController现在直接使用PermissionResolver接口,简化了菜单树的获取逻辑,提高了代码的可维护性和测试性。

图表来源

  • SystemController.java:40-44

认证流程重构(AuthServiceImpl)

更新 AuthServiceImpl进行了重大重构,优化了第三方登录流程和事务处理:

主要改进

  • 事务分离:DB写入在事务中完成,Redis token写入在事务外执行,避免脏数据问题
  • 组织准入预校验:仅在首登注册前触发钉钉组织通讯录校验,减少HTTP调用开销
  • 身份定位优化:统一的locateUser方法供只读探测和登录定位共用,避免逻辑漂移
  • 孤儿身份处理:自动重定向指向已删除用户的身份绑定到新注册用户

登录流程

  1. 前置校验:验证authCode非空
  2. 三方用户信息获取:通过clientFactory获取对应平台的用户信息
  3. 组织准入预校验:仅本地无匹配用户时触发
  4. 事务内登录逻辑:定位用户 + 校验状态 + 刷新登录时间
  5. 签发token:在事务外执行,失败不影响DB数据
flowchart TD
Start(["登录请求"]) --> Validate["验证authCode"]
Validate --> GetUserInfo["获取三方用户信息"]
GetUserInfo --> PreCheck{"本地有匹配用户?"}
PreCheck --> |是| SkipOrgCheck["跳过组织校验"]
PreCheck --> |否| OrgCheck["组织准入预校验"]
SkipOrgCheck --> DoLogin["事务内登录逻辑"]
OrgCheck --> DoLogin
DoLogin --> LocateUser["定位用户"]
LocateUser --> CheckEnabled{"用户是否启用?"}
CheckEnabled --> |否| ThrowError["抛出禁用错误"]
CheckEnabled --> |是| UpdateLoginTime["刷新登录时间"]
UpdateLoginTime --> CreateToken["签发token"]
CreateToken --> ReturnResult["返回登录结果"]

更新 重构后的认证流程更加健壮,事务处理避免了脏数据,组织准入预校验提升了性能,身份定位逻辑更加清晰。

图表来源

  • AuthServiceImpl.java:64-93
  • AuthServiceImpl.java:98-111
  • AuthServiceImpl.java:117-139

三层资源层级结构设计

系统现在支持完整的三层资源层级结构,通过MenuType枚举定义了目录、菜单、按钮三种资源类型:

  • 目录(CATALOG):作为收纳容器,不承载页面,只能挂menu子节点
  • 菜单(MENU):可导航的页面,只能挂button子节点
  • 按钮(BUTTON):叶子节点,不可再挂子节点,承载权限码和接口映射
classDiagram
class MenuType {
+CATALOG : MenuType
+MENU : MenuType
+BUTTON : MenuType
+fromCode(code) : MenuType
+isLegalChild(parent, child) : boolean
}
class SysMenu {
+Long parentId
+String menuName
+Integer menuType
+String path
+String component
+String icon
+Integer sort
+Boolean visible
+String description
+String perms
+String denyBehavior
+String status
+String apiUrl
+SysMenu[] children
}
class ResourceNode {
+Long id
+Long parentId
+String name
+MenuType type
+Integer sort
+String description
+String route
+String perms
+String denyBehavior
+String apiUrl
+String status
+String icon
+ResourceNode[] children
+fromEntity(entity) : ResourceNode
+toEntity() : SysMenu
}
MenuType <|-- SysMenu
SysMenu <..> ResourceNode : 双向转换

更新 三层资源层级结构在PermissionResolverImpl中得到了充分利用,visibleMenuTree方法会过滤掉button类型的菜单,只返回catalog和menu类型的菜单树。

图表来源

  • MenuType.java:12-52
  • SysMenu.java:12-76
  • ResourceNode.java:16-84

SysMenu实体增强设计

SysMenu实体经过大幅增强,现在支持按钮级别的细粒度权限控制:

  • 基础字段:parentId、menuName、menuType、path、component、icon、sort、visible
  • 按钮级权限字段:description(描述/备注)、perms(权限码)、denyBehavior(拒绝行为)、status(权限点状态)、apiUrl(接口URL)
  • 树形结构支持:children字段用于TreeUtils构建树时填充

更新 SysMenu实体现在具备完整的按钮级权限控制能力,包括详细的权限描述、权限码、前端行为和API映射。

章节来源

  • SysMenu.java:12-76

ResourceNode DTO层次结构支持

ResourceNode DTO现在支持完整的层次结构,包含children字段:

  • 层次结构字段List<ResourceNode> children - 子节点列表,仅在listAll()返回时填充
  • 双向转换方法
    • fromEntity方法:将SysMenu实体转换为ResourceNode DTO
    • toEntity方法:将ResourceNode DTO转换回SysMenu实体
  • 字段映射:自动处理name↔menuName、route↔path、type↔menuType等字段映射

更新 ResourceNode DTO现在支持层次结构,简化了前后端数据交互,提供了灵活的DTO转换机制和完整的树形数据支持。

章节来源

  • ResourceNode.java:16-84

统一资源树管理系统

完整的资源树管理系统,包含控制器、服务和接口,遵循全局接口契约:

ResourceController

提供标准化的RESTful API接口,采用显式动作后缀设计:

  • GET /api/resources/list获取全量资源树(返回嵌套层次结构)
  • POST /api/resources/saveOrUpdate:新增或编辑节点
  • POST /api/resources/delete:删除节点
  • POST /api/resources/icon/upload:图标上传

ResourceServiceImpl

实现完整的业务逻辑:

  • 层级约束验证:确保父子关系的合法性,使用MenuType.isLegalChild()进行统一验证
  • 字段必填校验:根据不同类型验证必填字段
  • 级联清理:删除时自动清理角色授权引用
  • 图标上传:支持多种格式的图标文件上传
  • 树形结构构建:使用TreeUtils构建真正的层次结构
  • 缓存失效:保存和删除操作后调用apiPermissionCache.invalidate()
flowchart TD
Start(["保存资源节点"]) --> Validate["参数校验"]
Validate --> TypeCheck{"检查节点类型"}
TypeCheck --> |目录| CatalogRule["目录规则验证"]
TypeCheck --> |菜单| MenuRule["菜单规则验证"]
TypeCheck --> |按钮| ButtonRule["按钮规则验证"]
CatalogRule --> HierarchyCheck["层级约束检查"]
MenuRule --> HierarchyCheck
ButtonRule --> HierarchyCheck
HierarchyCheck --> ApplyMapping["DTO→Entity映射"]
ApplyMapping --> Save["保存至数据库"]
Save --> InvalidateCache["失效API权限缓存"]
InvalidateCache --> Return["返回ResourceNode"]

更新 资源树管理系统提供了完善的CRUD操作和严格的业务规则验证。validateHierarchy方法现在使用MenuType.isLegalChild()进行层级验证,避免了硬编码的魔术数字,提高了代码的可维护性和可读性。新增的缓存失效机制确保权限规则实时更新。

图表来源

  • ResourceController.java:1-56
  • ResourceServiceImpl.java:55-97
  • IResourceService.java:1-26

数据权限范围控制(DataScopeEnum 与拦截器)

  • 数据权限类型
    • 全部数据、本部门数据、本部门及子部门数据、仅本人数据等
  • 注解驱动
    • 在方法上使用 @DataScope 指定数据范围与目标表
  • 拦截与SQL改写
    • DataScopeInterceptor 读取上下文中的数据权限范围
    • DataScopeTables 维护表与列的映射关系
    • 通过 MyBatis Plus 的 SQL 注入器自动追加 WHERE 条件
sequenceDiagram
participant Controller as "业务控制器"
participant Interceptor as "DataScopeInterceptor"
participant Helper as "DataScopeHelper"
participant Tables as "DataScopeTables"
participant MP as "MyBatisPlus注入器"
participant DB as "数据库"
Controller->>Interceptor : "进入受保护方法"
Interceptor->>Helper : "解析@DataScope参数"
Helper->>Tables : "查找表/列映射"
Tables-->>Helper : "返回映射信息"
Helper-->>MP : "生成数据权限WHERE片段"
MP-->>DB : "执行带限制的SQL"
DB-->>Controller : "返回受限结果"

图表来源

  • DataScope.java:1-200
  • DataScopeInterceptor.java:1-200
  • DataScopeTables.java:1-200
  • DataScopeHelper.java:1-200
  • CrmSqlInjector.java:1-200

章节来源

  • DataScopeEnum.java:1-19
  • DataScope.java:1-200
  • DataScopeInterceptor.java:1-200
  • DataScopeTables.java:1-200
  • DataScopeHelper.java:1-200
  • CrmSqlInjector.java:1-200

认证与安全配置(SecurityConfig、JwtAuthenticationFilter、TokenService)

  • SecurityConfig:定义URL白名单、授权规则、跨域与异常处理器
  • JwtAuthenticationFilter:解析JWT,填充认证上下文,集成新的PermissionResolver
  • TokenService:生成、刷新、注销令牌,维护会话状态
classDiagram
class SecurityConfig {
+configure(http) void
+cors() CorsConfigurationSource
+exceptionHandler() SecurityExceptionHandlers
}
class JwtAuthenticationFilter {
+doFilter(request, response, chain) void
+parseToken(request) String
+setContext(token) void
+permissionResolver : PermissionResolver
}
class TokenService {
+createToken(user) String
+refreshToken(token) String
+logout(token) void
+validate(token) boolean
}
SecurityConfig --> JwtAuthenticationFilter : "注册过滤器"
JwtAuthenticationFilter --> TokenService : "验证/解析令牌"
JwtAuthenticationFilter --> PermissionResolver : "权限解析"

图表来源

  • SecurityConfig.java:1-200
  • JwtAuthenticationFilter.java:1-66
  • TokenService.java:1-200

章节来源

  • SecurityConfig.java:1-200
  • JwtAuthenticationFilter.java:1-66
  • TokenService.java:1-200

权限注解使用与最佳实践

  • 接口级权限控制
    • 在需要权限校验的方法上标注权限标识(如菜单code或按钮标识)
    • 结合白名单跳过敏感接口校验
  • 数据权限注解
    • 在查询方法上标注 @DataScope,指定范围类型与目标表
    • 确保 DataScopeTables 中已配置对应表的列映射
  • 注意事项
    • 避免在事务内频繁切换数据权限上下文
    • 合理设置缓存失效时机,保证权限变更后即时生效

更新 基于新的PermissionResolver架构,权限控制更加模块化和安全化,fail-closed原则确保了权限解析失败的请求会被拒绝。新增的ApiPermissionInterceptor提供了URL级别的权限控制,与注解方式互补使用。

章节来源

  • DataScope.java:1-200
  • DataScopeTables.java:1-200
  • PermissionResolver.java:16-35

动态权限加载与缓存机制

  • 动态加载
    • 登录后根据用户角色拉取菜单树与按钮权限
    • 将权限集合与菜单树写入缓存
  • 缓存键设计
    • 用户维度:user:{userId}:perms
    • 角色维度:role:{roleId}:menuTree
  • 缓存更新
    • 角色/菜单变更触发失效
    • 提供手动刷新接口供管理员使用

更新 新的PermissionResolverImpl实现了共享查询链优化,减少了重复查询,提高了性能表现。新增的ApiPermissionCache基于Caffeine提供进程内缓存,支持懒加载和主动失效。

章节来源

  • PermissionResolverImpl.java:118-139
  • ApiPermissionCache.java:37-63
  • SysMenuServiceImpl.java:1-200
  • SysRoleServiceImpl.java:1-200

配置方法与初始化

  • 权限配置
    • PermissionConfig:集中管理权限相关开关与默认值
  • 安全配置
    • SecurityConfig:URL白名单、跨域、异常处理器
  • 数据初始化
    • DataInitializer:初始化默认角色、菜单、管理员账号等

更新 DataInitializer现在支持三层资源层级结构的初始化,包括目录、菜单和按钮的完整权限体系。新增的WebMvcConfig负责注册API权限拦截器。

章节来源

  • PermissionConfig.java:1-200
  • SecurityConfig.java:1-200
  • DataInitializer.java:1-76
  • WebMvcConfig.java:19-23

资源树层级约束验证机制

资源树的层级约束验证机制通过MenuType枚举和validateHierarchy方法实现:

  • 根节点验证:根节点不允许添加button类型
  • 父节点类型检查:确保父节点存在且类型有效
  • 层级关系验证:使用MenuType.isLegalChild()方法进行统一的层级关系验证
  • 空值安全处理:防止未知菜单类型导致的NullPointerException
flowchart TD
Start(["验证层级关系"]) --> RootCheck{"是否根节点?"}
RootCheck --> |是| RootRule["根节点规则检查"]
RootCheck --> |否| ParentCheck["父节点存在性检查"]
RootCheck --> |否| ParentTypeCheck["父节点类型检查"]
ParentTypeCheck --> UnknownType{"父节点类型是否已知?"}
UnknownType --> |否| Error["抛出未知类型错误"]
UnknownType --> |是| LegalCheck["使用MenuType.isLegalChild验证"]
LegalCheck --> Valid{"层级关系合法?"}
Valid --> |是| Success["验证通过"]
Valid --> |否| ErrorMsg["抛出具体错误消息"]
RootRule --> Success

更新 validateHierarchy方法现在完全依赖MenuType.isLegalChild()进行层级验证,消除了硬编码的魔术数字,提高了代码的可维护性和一致性。

图表来源

  • ResourceServiceImpl.java:174-202
  • MenuType.java:37-51

章节来源

  • ResourceServiceImpl.java:174-202
  • MenuType.java:37-51

路由路径处理逻辑改进

改进了路由路径处理逻辑,避免空字符串覆盖数据库值:

  • 非空值覆盖原则:只有当node.getRoute()为非空时才覆盖entity.getPath()
  • 空串保留机制:空字符串或空白字符不会覆盖数据库中已有的path值
  • 编辑模式支持:在编辑模式下,如果未提供route但数据库已有path则保持原值

这种改进确保了数据的一致性和完整性,避免了意外覆盖数据库中的有效数据。

章节来源

  • ResourceServiceImpl.java:243-246

API设计标准化

资源管理系统的API设计遵循全局接口契约,采用显式动作后缀模式:

  • GET /api/resources/list获取全量资源树,返回嵌套层次结构
  • POST /api/resources/saveOrUpdate:新增或编辑节点,支持表单字段接收
  • POST /api/resources/delete:删除节点,通过@RequestParam接收ID参数
  • POST /api/resources/icon/upload:图标上传,支持MultipartFile文件处理

这种设计确保了与应用程序其他部分的API设计一致性,避免了传统RESTful约定的歧义。

更新 ResourceController严格遵循全局接口契约,所有写操作都使用POST + 动作后缀的模式,提升了API的一致性和可预测性。

章节来源

  • ResourceController.java:1-56

树形结构构建机制

基于TreeUtils的高效树形结构构建机制:

  • O(n)复杂度算法:相比旧版O(n²)算法,性能显著提升
  • 泛型支持:支持Long/String等多种主键类型
  • 灵活配置:通过函数式接口支持自定义的getter/setter方法
  • 智能分组:按parentId分组后一次性挂载子节点
flowchart TD
Start(["开始构建树"]) --> EmptyCheck{"列表是否为空?"}
EmptyCheck --> |是| ReturnEmpty["返回空列表"]
EmptyCheck --> |否| IdCollection["收集所有节点ID"]
IdCollection --> GroupByParent["按parentId分组"]
GroupByParent --> MountChildren["挂载子节点"]
MountChildren --> IdentifyRoots["识别顶级节点"]
IdentifyRoots --> ReturnTree["返回树形结构"]

更新 TreeUtils工具类提供了高效的树形结构构建能力,PermissionResolverImpl中使用它来构建菜单树,ResourceServiceImpl中使用它来构建资源树。

图表来源

  • TreeUtils.java:36-71
  • PermissionResolverImpl.java:115

章节来源

  • TreeUtils.java:1-91
  • PermissionResolverImpl.java:115

依赖关系分析

  • 模块依赖
    • crm-auth 依赖 crm-base(异常、工具、数据可见性上下文)
    • crm-app 依赖 crm-auth(作为应用入口)
  • 组件耦合
    • PermissionResolverImpl 依赖多个Mapper和DeptTreeCache
    • JwtAuthenticationFilter 依赖PermissionResolver和TokenService
    • SystemController 依赖PermissionResolver和其他服务
    • ApiPermissionInterceptor 依赖ApiPermissionCache
    • ResourceServiceImpl 依赖ApiPermissionCache进行缓存失效
    • DataScopeInterceptor 依赖 DataScopeHelper 与 CrmSqlInjector
    • SecurityConfig 依赖 JwtAuthenticationFilter 与异常处理器
graph LR
APP["crm-app"] --> AUTH["crm-auth"]
AUTH --> BASE["crm-base"]
AUTH --> |"依赖"| Mapper["Mapper层"]
AUTH --> |"依赖"| Service["Service层"]
Service --> |"依赖"| Mapper
ResourceController --> ResourceService
ResourceService --> SysMenuService
ResourceService --> FileApi
ResourceService --> ApiPermissionCache
JwtAuthenticationFilter --> PermissionResolver
PermissionResolver --> PermissionResolverImpl
SystemController --> PermissionResolver
ApiPermissionInterceptor --> ApiPermissionCache

更新 新的架构中,PermissionResolverImpl解耦了具体的权限解析逻辑,JwtAuthenticationFilter和SystemController都通过PermissionResolver接口进行依赖,降低了耦合度。新增的ApiPermissionInterceptor依赖ApiPermissionCache,ResourceServiceImpl通过ApiPermissionCache实现缓存失效。

图表来源

  • CrmAppApplication.java:1-200
  • AuthApplication.java:1-200

章节来源

  • CrmAppApplication.java:1-200
  • AuthApplication.java:1-200

性能考虑

  • 缓存优先
    • 菜单树与权限集合应缓存至Redis,降低重复查询
    • 合理设置过期时间与失效策略
  • 批量与懒加载
    • 菜单树构建尽量使用批量查询与懒加载
  • 数据权限SQL优化
    • 在 DataScopeTables 中精确映射表与列,避免全表扫描
    • 为常用过滤列添加索引
  • 新增 资源树操作优化
    • TreeUtils提供O(n)复杂度的树构建算法,性能显著提升
    • listAll()方法直接返回树形结构,减少前端处理开销
    • 图标上传异步处理提升用户体验
    • 使用MenuType枚举提高类型检查效率
  • 新增 API权限拦截优化
    • ApiPermissionCache基于Caffeine提供高性能进程内缓存
    • 懒加载机制避免不必要的数据库查询
    • AntPathMatcher提供高效的URL模式匹配

更新 新的PermissionResolverImpl实现了共享查询链优化,通过authorizedMenus()方法复用查询结果,减少了重复的数据库访问。同时,PermissionGrant作为不可变值对象,避免了不必要的对象创建和修改开销。新增的ApiPermissionCache基于Caffeine提供高性能缓存,懒加载机制确保只在首次访问时加载规则。

故障排查指南

  • 常见错误
    • 未认证或令牌过期:检查 JwtAuthenticationFilter 与 TokenService 配置
    • 权限不足:确认角色-菜单授权是否正确,缓存是否及时失效
    • 数据权限不生效:检查 @DataScope 注解与 DataScopeTables 映射
    • 新增 权限解析失败:检查PermissionResolverImpl的实现和依赖注入
    • 新增 API权限拦截403:检查ApiPermissionCache规则和用户权限
    • 新增 认证流程异常:检查AuthServiceImpl的事务处理和第三方登录逻辑
  • 定位步骤
    • 查看全局异常处理器输出
    • 开启调试日志,观察拦截器与SQL注入过程
    • 核对缓存键与失效时机
    • 新增 检查PermissionResolverImpl中的权限解析逻辑
    • 新增 验证PermissionGrant的值对象构造和数据完整性
    • 新增 确认JwtAuthenticationFilter中的fail-closed逻辑
    • 新增 检查SystemController中PermissionResolver的使用
    • 新增 验证ApiPermissionInterceptor的URL匹配逻辑
    • 新增 检查ResourceServiceImpl中的缓存失效调用

更新 对于新的三组件架构,建议特别关注PermissionResolverImpl的依赖注入是否正确,PermissionGrant的构造参数是否完整,以及JwtAuthenticationFilter中的fail-closed逻辑是否正常工作。新增的API权限拦截系统需要检查ApiPermissionCache的规则加载和用户权限匹配。

章节来源

  • GlobalExceptionHandlerAdvice.java:1-200
  • JwtAuthenticationFilter.java:1-66
  • TokenService.java:1-200
  • DataScopeInterceptor.java:1-200
  • DataScopeTables.java:1-200
  • PermissionResolverImpl.java:1-140
  • ApiPermissionInterceptor.java:38-80
  • AuthServiceImpl.java:64-93

结论

本权限管理系统以RBAC为核心,结合注解驱动的数据权限控制与拦截器实现的SQL改写,形成完整的权限校验与数据隔离方案。通过本次重构,系统实现了PermissionResolver三组件架构,包括PermissionResolver接口、PermissionGrant不可变值对象和PermissionResolverImpl实现类,显著提升了权限解析的模块化设计和安全性。

最重要的改进是新的三组件架构采用了fail-closed安全原则,确保权限解析失败的请求会被拒绝;PermissionGrant作为不可变值对象保证了数据的一致性和线程安全;PermissionResolverImpl实现了共享查询链优化,减少了重复查询开销。 通过JwtAuthenticationFilter和SystemController对PermissionResolver的统一使用,简化了权限解析的调用方式,提高了代码的可维护性和测试性。

新增的动态API权限拦截系统通过ApiPermissionInterceptor和ApiPermissionCache提供了URL级别的权限控制,基于Caffeine缓存保证性能,fail-open策略确保向后兼容性。 资源树变更时自动触发缓存失效,确保权限规则实时更新。

改进的认证流程重构优化了事务处理,避免了脏数据问题,组织准入预校验提升了性能,身份定位逻辑更加清晰。 新的架构还充分利用了三层资源层级结构(目录-菜单-按钮),在visibleMenuTree方法中正确过滤button类型的菜单,只返回catalog和menu类型的菜单树。TreeUtils工具类的O(n)算法相比之前的O(n²)算法有显著的性能提升。通过对PermissionResolverImpl中authorizedMenus方法的优化,实现了用户→角色→授权→资源的共享查询链,进一步提升了性能表现。

通过合理的缓存策略与配置化开关,可在保障安全性的同时提升性能与可维护性。建议在生产环境严格管理权限变更与缓存失效,确保权限一致性与实时性。

附录

  • 术语
    • RBAC:基于角色的访问控制
    • 数据权限:基于用户所属组织或范围的查询限制
    • fail-closed:失败关闭原则,权限解析失败即拒绝请求
    • fail-open:失败开放原则,未匹配规则时放行请求
    • 不可变值对象:一旦创建就不能修改的对象
    • AntPathMatcher:Spring提供的URL模式匹配器
  • 参考
    • 实体与枚举:SysRole、SysMenu、DataScopeEnum、MenuType
    • 数据传输对象:ResourceNode
    • 新增 权限解析三组件:PermissionResolver、PermissionGrant、PermissionResolverImpl
    • 新增 动态API权限拦截:ApiPermissionInterceptor、ApiPermissionCache、ApiPermissionRule
    • 服务与拦截:DataScopeInterceptor、JwtAuthenticationFilter
    • 新增 系统集成:SystemController对PermissionResolver的使用
    • 工具类:TreeUtils
    • 配置与初始化:PermissionConfig、SecurityConfig、DataInitializer、WebMvcConfig

更新 重点关注新的PermissionResolver三组件架构,它们提供了更强大的权限解析能力和更好的模块化设计。PermissionGrant不可变值对象确保了数据安全,PermissionResolverImpl的共享查询链优化提升了性能。SystemController对PermissionResolver的直接使用简化了调用方式,提高了代码的可维护性。新增的ApiPermissionInterceptor和ApiPermissionCache提供了灵活的URL级别权限控制,AuthServiceImpl的重构优化了认证流程。