# 动态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权限解耦但同属安全域) ```mermaid graph TB subgraph "crm-auth" A["ApiPermissionInterceptor
拦截器"] B["ApiPermissionCache
规则缓存"] C["ApiPermissionRule
规则记录"] D["WebMvcConfig
拦截器注册"] E["ResourceServiceImpl
资源服务(失效缓存)"] F["SysMenu
菜单实体(apiUrl/perms)"] G["MenuType
菜单类型枚举"] H["PermissionResolver / Impl
权限解析引擎"] I["PermissionGrant
授权结果"] end subgraph "crm-base" J["DataVisibilityContext
数据可见性上下文"] K["DataVisibility
数据可见性模型"] L["DataScopeLevel
数据范围档位"] end D --> A A --> B B --> F B --> G E --> B H --> I I --> J J --> K K --> L ``` **图表来源** - [ApiPermissionInterceptor.java:1-82](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java#L1-L82) - [ApiPermissionCache.java:1-65](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) - [ApiPermissionRule.java:1-11](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionRule.java#L1-L11) - [WebMvcConfig.java:1-25](file://crm-auth/src/main/java/com/crm/auth/config/WebMvcConfig.java#L1-L25) - [ResourceServiceImpl.java:1-200](file://crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java#L1-L200) - [SysMenu.java:1-77](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java#L1-L77) - [MenuType.java:1-52](file://crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java#L1-L52) - [PermissionResolver.java:1-36](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java#L1-L36) - [PermissionResolverImpl.java:1-140](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java#L1-L140) - [PermissionGrant.java:1-68](file://crm-auth/src/main/java/com/crm/auth/security/PermissionGrant.java#L1-L68) - [DataVisibilityContext.java:1-45](file://crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java#L1-L45) - [DataVisibility.java:1-75](file://crm-base/src/main/java/com/crm/base/security/DataVisibility.java#L1-L75) - [DataScopeLevel.java:1-51](file://crm-base/src/main/java/com/crm/base/security/DataScopeLevel.java#L1-L51) **章节来源** - [WebMvcConfig.java:1-25](file://crm-auth/src/main/java/com/crm/auth/config/WebMvcConfig.java#L1-L25) - [ApiPermissionInterceptor.java:1-82](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java#L1-L82) - [ApiPermissionCache.java:1-65](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) ## 核心组件 - 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](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java#L1-L82) - [ApiPermissionCache.java:1-65](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) - [ApiPermissionRule.java:1-11](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionRule.java#L1-L11) - [WebMvcConfig.java:1-25](file://crm-auth/src/main/java/com/crm/auth/config/WebMvcConfig.java#L1-L25) - [ResourceServiceImpl.java:1-200](file://crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java#L1-L200) - [SysMenu.java:1-77](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java#L1-L77) - [MenuType.java:1-52](file://crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java#L1-L52) - [PermissionResolver.java:1-36](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java#L1-L36) - [PermissionResolverImpl.java:1-140](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java#L1-L140) - [PermissionGrant.java:1-68](file://crm-auth/src/main/java/com/crm/auth/security/PermissionGrant.java#L1-L68) - [DataVisibilityContext.java:1-45](file://crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java#L1-L45) - [DataVisibility.java:1-75](file://crm-base/src/main/java/com/crm/base/security/DataVisibility.java#L1-L75) - [DataScopeLevel.java:1-51](file://crm-base/src/main/java/com/crm/base/security/DataScopeLevel.java#L1-L51) ## 架构总览 下图展示了请求进入后的整体流程:JWT过滤器完成认证后,MVC拦截器根据缓存的规则进行权限判定;资源树变更后由服务层触发缓存失效。 ```mermaid 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](file://crm-auth/src/main/java/com/crm/auth/config/WebMvcConfig.java#L1-L25) - [ApiPermissionInterceptor.java:1-82](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java#L1-L82) - [ApiPermissionCache.java:1-65](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) - [SysMenu.java:1-77](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java#L1-L77) ## 详细组件分析 ### 拦截器:ApiPermissionInterceptor - 职责:在请求进入Controller前,从SecurityContext获取用户权限码集合,结合缓存规则进行匹配校验 - 匹配策略:AntPathMatcher支持*与**通配;若未匹配任何规则则放行(fail-open) - 失败处理:匹配到规则但无对应权限时返回403,并写入统一JSON提示 ```mermaid 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](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java#L1-L82) **章节来源** - [ApiPermissionInterceptor.java:1-82](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java#L1-L82) ### 规则缓存:ApiPermissionCache - 职责:以单key形式缓存全部规则列表,首次访问懒加载,后续直接命中 - 数据来源:查询sys_menu中类型为button、状态enabled且包含apiUrl的记录,映射为规则 - 失效机制:资源树变更时调用invalidate清空缓存,下次请求重建 ```mermaid 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](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) - [ApiPermissionRule.java:1-11](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionRule.java#L1-L11) - [SysMenu.java:1-77](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java#L1-L77) **章节来源** - [ApiPermissionCache.java:1-65](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) - [SysMenu.java:1-77](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java#L1-L77) - [MenuType.java:1-52](file://crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java#L1-L52) ### 资源服务与缓存失效:ResourceServiceImpl - 职责:对资源树进行增删改操作,并在保存/删除后主动失效权限规则缓存 - 关键点:save与delete均调用apiPermissionCache.invalidate(),确保下一次请求重建规则 ```mermaid 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](file://crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java#L1-L200) - [ApiPermissionCache.java:1-65](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) **章节来源** - [ResourceServiceImpl.java:1-200](file://crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java#L1-L200) ### 权限解析引擎:PermissionResolver/Impl/Grant - 职责:一次resolve计算用户的“数据可见性+权限码并集+角色编码”,供登录前后端渲染与鉴权使用 - 输出:PermissionGrant封装了DataVisibility、permCodes与roleCodes,并提供asAuthorities生成Spring Security authorities ```mermaid classDiagram class PermissionResolver { <> +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 { <> +SELF +DEPT +DEPT_AND_CHILDREN +ALL } PermissionResolver <|.. PermissionResolverImpl PermissionResolverImpl --> PermissionGrant : "返回" PermissionGrant --> DataVisibility : "包含" DataVisibility --> DataScopeLevel : "引用" ``` **图表来源** - [PermissionResolver.java:1-36](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java#L1-L36) - [PermissionResolverImpl.java:1-140](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java#L1-L140) - [PermissionGrant.java:1-68](file://crm-auth/src/main/java/com/crm/auth/security/PermissionGrant.java#L1-L68) - [DataVisibility.java:1-75](file://crm-base/src/main/java/com/crm/base/security/DataVisibility.java#L1-L75) - [DataScopeLevel.java:1-51](file://crm-base/src/main/java/com/crm/base/security/DataScopeLevel.java#L1-L51) **章节来源** - [PermissionResolver.java:1-36](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java#L1-L36) - [PermissionResolverImpl.java:1-140](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java#L1-L140) - [PermissionGrant.java:1-68](file://crm-auth/src/main/java/com/crm/auth/security/PermissionGrant.java#L1-L68) - [DataVisibility.java:1-75](file://crm-base/src/main/java/com/crm/base/security/DataVisibility.java#L1-L75) - [DataScopeLevel.java:1-51](file://crm-base/src/main/java/com/crm/base/security/DataScopeLevel.java#L1-L51) ### 数据可见性上下文:DataVisibilityContext - 职责:以ThreadLocal存储当前请求的数据可见性范围,供数据权限拦截器与公共字段填充器读取 - 生命周期:请求开始时装载,结束时清理;异步场景需显式重新装载 **章节来源** - [DataVisibilityContext.java:1-45](file://crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java#L1-L45) ## 依赖关系分析 - 拦截器依赖缓存,缓存依赖菜单实体与类型枚举 - 资源服务依赖菜单服务与缓存,用于触发失效 - 权限解析引擎依赖多个Mapper与部门树缓存,产出授权结果 - MVC配置将拦截器注册到/api/**路径 ```mermaid 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](file://crm-auth/src/main/java/com/crm/auth/config/WebMvcConfig.java#L1-L25) - [ApiPermissionInterceptor.java:1-82](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java#L1-L82) - [ApiPermissionCache.java:1-65](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) - [SysMenu.java:1-77](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java#L1-L77) - [MenuType.java:1-52](file://crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java#L1-L52) - [ResourceServiceImpl.java:1-200](file://crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java#L1-L200) - [PermissionResolverImpl.java:1-140](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java#L1-L140) - [PermissionGrant.java:1-68](file://crm-auth/src/main/java/com/crm/auth/security/PermissionGrant.java#L1-L68) - [DataVisibility.java:1-75](file://crm-base/src/main/java/com/crm/base/security/DataVisibility.java#L1-L75) - [DataScopeLevel.java:1-51](file://crm-base/src/main/java/com/crm/base/security/DataScopeLevel.java#L1-L51) **章节来源** - [PermissionConfig.java:1-55](file://crm-auth/src/main/java/com/crm/auth/config/PermissionConfig.java#L1-L55) ## 性能考量 - 规则缓存:采用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](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java#L1-L82) - [ApiPermissionCache.java:1-65](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java#L1-L65) - [ResourceServiceImpl.java:1-200](file://crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java#L1-L200) ## 结论 动态API权限拦截器通过“规则缓存+拦截器匹配”的方式,实现了灵活、可配置的接口级权限控制。其与数据权限体系解耦,既保证了安全性,又具备良好的扩展性与性能表现。配合资源树管理与缓存失效机制,能够在运行时动态调整权限策略,满足复杂业务场景需求。 [本节为总结性内容,不直接分析具体文件] ## 附录 - 单元测试用例覆盖了典型场景:未注册URL放行、匹配且有权限放行、匹配但无权限拒绝、Ant通配匹配、停用节点不生成规则、多规则任一权限放行等 **章节来源** - [ApiPermissionInterceptorTest.java:1-162](file://crm-auth/src/test/java/com/crm/auth/security/ApiPermissionInterceptorTest.java#L1-L162)