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.
20 KiB
20 KiB
动态API权限拦截器
**本文引用的文件** - [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) - [ResourceServiceImpl.java](file://crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java) - [SysMenu.java](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java) - [MenuType.java](file://crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java) - [PermissionResolver.java](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java) - [PermissionResolverImpl.java](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java) - [PermissionGrant.java](file://crm-auth/src/main/java/com/crm/auth/security/PermissionGrant.java) - [DataVisibilityContext.java](file://crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java) - [DataVisibility.java](file://crm-base/src/main/java/com/crm/base/security/DataVisibility.java) - [DataScopeLevel.java](file://crm-base/src/main/java/com/crm/base/security/DataScopeLevel.java) - [PermissionConfig.java](file://crm-auth/src/main/java/com/crm/auth/config/PermissionConfig.java) - [ApiPermissionInterceptorTest.java](file://crm-auth/src/test/java/com/crm/auth/security/ApiPermissionInterceptorTest.java)目录
简介
本文件围绕“动态API权限拦截器”的实现与使用进行系统化说明。该能力通过MVC拦截器在请求进入Controller之前,依据用户已授权的权限码集合与缓存的API规则(来自菜单资源树中的按钮节点)进行匹配校验,实现细粒度的接口级访问控制。其关键特性包括:
- 基于Ant风格路径模式匹配(支持*与**)
- fail-open策略:未注册规则的URL默认放行
- 进程内Caffeine缓存规则列表,资源变更时主动失效
- 与Spring Security上下文集成,从当前认证主体提取权限码集合
- 与数据权限体系解耦,仅负责“是否允许调用某接口”的判定
项目结构
围绕动态API权限拦截器的相关代码分布在以下模块与包中:
- crm-auth/security:拦截器、规则、缓存、权限解析等核心安全组件
- crm-auth/config:MVC拦截器注册配置
- crm-auth/service/impl:资源树服务,负责保存/删除后触发权限规则缓存失效
- crm-auth/domain/entity & enums:菜单实体与类型枚举,承载apiUrl与perms等字段
- crm-base/security:数据可见性上下文与范围模型(与API权限解耦但同属安全域)
graph TB
subgraph "crm-auth"
A["ApiPermissionInterceptor<br/>拦截器"]
B["ApiPermissionCache<br/>规则缓存"]
C["ApiPermissionRule<br/>规则记录"]
D["WebMvcConfig<br/>拦截器注册"]
E["ResourceServiceImpl<br/>资源服务(失效缓存)"]
F["SysMenu<br/>菜单实体(apiUrl/perms)"]
G["MenuType<br/>菜单类型枚举"]
H["PermissionResolver / Impl<br/>权限解析引擎"]
I["PermissionGrant<br/>授权结果"]
end
subgraph "crm-base"
J["DataVisibilityContext<br/>数据可见性上下文"]
K["DataVisibility<br/>数据可见性模型"]
L["DataScopeLevel<br/>数据范围档位"]
end
D --> A
A --> B
B --> F
B --> G
E --> B
H --> I
I --> J
J --> K
K --> L
图表来源
- ApiPermissionInterceptor.java:1-82
- ApiPermissionCache.java:1-65
- ApiPermissionRule.java:1-11
- WebMvcConfig.java:1-25
- ResourceServiceImpl.java:1-200
- SysMenu.java:1-77
- MenuType.java:1-52
- PermissionResolver.java:1-36
- PermissionResolverImpl.java:1-140
- PermissionGrant.java:1-68
- DataVisibilityContext.java:1-45
- DataVisibility.java:1-75
- DataScopeLevel.java:1-51
章节来源
- WebMvcConfig.java:1-25
- ApiPermissionInterceptor.java:1-82
- ApiPermissionCache.java:1-65
核心组件
- ApiPermissionInterceptor:MVC拦截器,负责请求前拦截、规则匹配、权限判定与响应处理
- ApiPermissionCache:进程内规则缓存,懒加载并支持主动失效
- ApiPermissionRule:规则记录(pattern→perms)
- WebMvcConfig:注册拦截器到/api/**路径
- ResourceServiceImpl:资源树CRUD后触发缓存失效
- SysMenu/MenuType:菜单实体与类型,承载apiUrl与perms等元数据
- PermissionResolver/Impl/Grant:权限解析引擎与结果对象(为前端菜单与角色/权限码聚合提供支撑)
- DataVisibilityContext/Visibility/Level:数据可见性与范围档位(与API权限解耦)
章节来源
- ApiPermissionInterceptor.java:1-82
- ApiPermissionCache.java:1-65
- ApiPermissionRule.java:1-11
- WebMvcConfig.java:1-25
- ResourceServiceImpl.java:1-200
- SysMenu.java:1-77
- MenuType.java:1-52
- PermissionResolver.java:1-36
- PermissionResolverImpl.java:1-140
- PermissionGrant.java:1-68
- DataVisibilityContext.java:1-45
- DataVisibility.java:1-75
- DataScopeLevel.java:1-51
架构总览
下图展示了请求进入后的整体流程:JWT过滤器完成认证后,MVC拦截器根据缓存的规则进行权限判定;资源树变更后由服务层触发缓存失效。
sequenceDiagram
participant Client as "客户端"
participant JWT as "JWT过滤器"
participant MVC as "WebMvcConfig"
participant Interceptor as "ApiPermissionInterceptor"
participant Cache as "ApiPermissionCache"
participant DB as "数据库(sys_menu)"
participant Controller as "业务Controller"
Client->>JWT : "HTTP 请求"
JWT-->>Client : "设置SecurityContext(含权限码)"
Client->>MVC : "路由分发"
MVC->>Interceptor : "preHandle(/api/**)"
Interceptor->>Cache : "getRules()"
alt "缓存命中"
Cache-->>Interceptor : "规则列表"
else "缓存未命中"
Cache->>DB : "查询启用且带apiUrl的按钮节点"
DB-->>Cache : "菜单节点集合"
Cache-->>Interceptor : "构建规则列表并缓存"
end
Interceptor->>Interceptor : "Ant路径匹配 + 权限码比对"
alt "有权限或无规则"
Interceptor-->>Controller : "放行"
Controller-->>Client : "业务响应"
else "无权限"
Interceptor-->>Client : "403 拒绝"
end
图表来源
- WebMvcConfig.java:1-25
- ApiPermissionInterceptor.java:1-82
- ApiPermissionCache.java:1-65
- SysMenu.java:1-77
详细组件分析
拦截器:ApiPermissionInterceptor
- 职责:在请求进入Controller前,从SecurityContext获取用户权限码集合,结合缓存规则进行匹配校验
- 匹配策略:AntPathMatcher支持*与**通配;若未匹配任何规则则放行(fail-open)
- 失败处理:匹配到规则但无对应权限时返回403,并写入统一JSON提示
flowchart TD
Start(["进入 preHandle"]) --> GetUri["获取请求URI"]
GetUri --> GetRules["从缓存获取规则列表"]
GetRules --> Match{"是否存在匹配规则?"}
Match --> |否| Allow["放行(fail-open)"]
Match --> |是| GetAuth["从SecurityContext取用户权限码集合"]
GetAuth --> CheckPerm{"任一规则 perms 是否在用户权限中?"}
CheckPerm --> |是| Allow
CheckPerm --> |否| Deny["返回403并写回JSON"]
Allow --> End(["结束"])
Deny --> End
图表来源
- ApiPermissionInterceptor.java:1-82
章节来源
- ApiPermissionInterceptor.java:1-82
规则缓存:ApiPermissionCache
- 职责:以单key形式缓存全部规则列表,首次访问懒加载,后续直接命中
- 数据来源:查询sys_menu中类型为button、状态enabled且包含apiUrl的记录,映射为规则
- 失效机制:资源树变更时调用invalidate清空缓存,下次请求重建
classDiagram
class ApiPermissionCache {
-SysMenuMapper sysMenuMapper
-Cache~String,List~ cache
+getRules() ApiPermissionRule[]
+invalidate() void
-loadRules() ApiPermissionRule[]
}
class ApiPermissionRule {
+String pattern
+String perms
}
class SysMenu {
+Integer menuType
+String status
+String apiUrl
+String perms
}
ApiPermissionCache --> ApiPermissionRule : "构建"
ApiPermissionCache --> SysMenu : "读取"
图表来源
- ApiPermissionCache.java:1-65
- ApiPermissionRule.java:1-11
- SysMenu.java:1-77
章节来源
- ApiPermissionCache.java:1-65
- SysMenu.java:1-77
- MenuType.java:1-52
资源服务与缓存失效:ResourceServiceImpl
- 职责:对资源树进行增删改操作,并在保存/删除后主动失效权限规则缓存
- 关键点:save与delete均调用apiPermissionCache.invalidate(),确保下一次请求重建规则
sequenceDiagram
participant Admin as "管理员"
participant Service as "ResourceServiceImpl"
participant MenuSvc as "ISysMenuService"
participant Cache as "ApiPermissionCache"
participant DB as "数据库"
Admin->>Service : "保存/删除资源节点"
Service->>MenuSvc : "持久化菜单实体"
MenuSvc-->>Service : "成功"
Service->>Cache : "invalidate()"
Cache-->>Service : "缓存已失效"
Service-->>Admin : "返回结果"
图表来源
- ResourceServiceImpl.java:1-200
- ApiPermissionCache.java:1-65
章节来源
- ResourceServiceImpl.java:1-200
权限解析引擎:PermissionResolver/Impl/Grant
- 职责:一次resolve计算用户的“数据可见性+权限码并集+角色编码”,供登录前后端渲染与鉴权使用
- 输出:PermissionGrant封装了DataVisibility、permCodes与roleCodes,并提供asAuthorities生成Spring Security authorities
classDiagram
class PermissionResolver {
<<interface>>
+resolve(userId) PermissionGrant
+visibleMenuTree(userId) SysMenu[]
}
class PermissionResolverImpl {
-SysUserRoleMapper
-SysRoleMapper
-SysRoleMenuMapper
-SysMenuMapper
-AuthUserMapper
-DeptTreeCache
-SysUserDeptMapper
+resolve(userId) PermissionGrant
+visibleMenuTree(userId) SysMenu[]
}
class PermissionGrant {
-DataVisibility visibility
-Set~String~ permCodes
-Set~String~ roleCodes
+asAuthorities() SimpleGrantedAuthority[]
}
class DataVisibility {
+visibilityScope() VisibilityScope
+dataOwnership() DataOwnership
}
class DataScopeLevel {
<<enum>>
+SELF
+DEPT
+DEPT_AND_CHILDREN
+ALL
}
PermissionResolver <|.. PermissionResolverImpl
PermissionResolverImpl --> PermissionGrant : "返回"
PermissionGrant --> DataVisibility : "包含"
DataVisibility --> DataScopeLevel : "引用"
图表来源
- PermissionResolver.java:1-36
- PermissionResolverImpl.java:1-140
- PermissionGrant.java:1-68
- DataVisibility.java:1-75
- DataScopeLevel.java:1-51
章节来源
- PermissionResolver.java:1-36
- PermissionResolverImpl.java:1-140
- PermissionGrant.java:1-68
- DataVisibility.java:1-75
- DataScopeLevel.java:1-51
数据可见性上下文:DataVisibilityContext
- 职责:以ThreadLocal存储当前请求的数据可见性范围,供数据权限拦截器与公共字段填充器读取
- 生命周期:请求开始时装载,结束时清理;异步场景需显式重新装载
章节来源
- DataVisibilityContext.java:1-45
依赖关系分析
- 拦截器依赖缓存,缓存依赖菜单实体与类型枚举
- 资源服务依赖菜单服务与缓存,用于触发失效
- 权限解析引擎依赖多个Mapper与部门树缓存,产出授权结果
- MVC配置将拦截器注册到/api/**路径
graph LR
WebMvcConfig --> ApiPermissionInterceptor
ApiPermissionInterceptor --> ApiPermissionCache
ApiPermissionCache --> SysMenu
ApiPermissionCache --> MenuType
ResourceServiceImpl --> ApiPermissionCache
PermissionResolverImpl --> SysMenu
PermissionResolverImpl --> SysRoleMenu
PermissionResolverImpl --> AuthUser
PermissionResolverImpl --> DeptTreeCache
PermissionGrant --> DataVisibility
DataVisibility --> DataScopeLevel
图表来源
- WebMvcConfig.java:1-25
- ApiPermissionInterceptor.java:1-82
- ApiPermissionCache.java:1-65
- SysMenu.java:1-77
- MenuType.java:1-52
- ResourceServiceImpl.java:1-200
- PermissionResolverImpl.java:1-140
- PermissionGrant.java:1-68
- DataVisibility.java:1-75
- DataScopeLevel.java:1-51
章节来源
- PermissionConfig.java:1-55
性能考量
- 规则缓存:采用Caffeine单key缓存,避免每次请求都查库;首次加载与失效后重建为唯一开销
- Ant匹配:内存中字符串匹配,复杂度与规则数量线性相关;建议合理拆分规则与避免过度通配
- 权限解析:一次resolve聚合多表查询,适合登录后一次性计算并复用;注意大数据量下的N+1问题(已通过批量查询优化)
- 线程上下文:DataVisibilityContext基于ThreadLocal,避免跨线程传递导致的额外开销
[本节为通用指导,不直接分析具体文件]
故障排查指南
- 现象:接口被403拒绝
- 检查用户是否拥有对应perms(可从SecurityContext或PermissionGrant中查看)
- 确认API URL是否与规则pattern匹配(Ant通配是否正确)
- 确认菜单节点状态是否为enabled且存在apiUrl
- 现象:修改资源后权限未生效
- 确认ResourceServiceImpl.save/delete是否调用了apiPermissionCache.invalidate()
- 检查缓存是否被其他逻辑覆盖或异常未失效
- 现象:未注册URL被拒绝
- 按fail-open设计,未匹配规则应放行;如被拒绝,检查是否有全局拦截器或网关层限制
章节来源
- ApiPermissionInterceptor.java:1-82
- ApiPermissionCache.java:1-65
- ResourceServiceImpl.java:1-200
结论
动态API权限拦截器通过“规则缓存+拦截器匹配”的方式,实现了灵活、可配置的接口级权限控制。其与数据权限体系解耦,既保证了安全性,又具备良好的扩展性与性能表现。配合资源树管理与缓存失效机制,能够在运行时动态调整权限策略,满足复杂业务场景需求。
[本节为总结性内容,不直接分析具体文件]
附录
- 单元测试用例覆盖了典型场景:未注册URL放行、匹配且有权限放行、匹配但无权限拒绝、Ant通配匹配、停用节点不生成规则、多规则任一权限放行等
章节来源
- ApiPermissionInterceptorTest.java:1-162