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

动态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)

目录

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

简介

本文件围绕“动态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