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权限拦截、认证流程和数据权限的完整测试场景
目录
简介
本文件为基于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方法供只读探测和登录定位共用,避免逻辑漂移
- 孤儿身份处理:自动重定向指向已删除用户的身份绑定到新注册用户
登录流程
- 前置校验:验证authCode非空
- 三方用户信息获取:通过clientFactory获取对应平台的用户信息
- 组织准入预校验:仅本地无匹配用户时触发
- 事务内登录逻辑:定位用户 + 校验状态 + 刷新登录时间
- 签发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 DTOtoEntity方法:将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的重构优化了认证流程。