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.
17 KiB
17 KiB
动态API权限拦截器
**本文引用的文件** - [ApiPermissionInterceptor.java](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java) - [ApiPermissionRule.java](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionRule.java) - [ApiPermissionCache.java](file://crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java) - [WebMvcConfig.java](file://crm-auth/src/main/java/com/crm/auth/config/WebMvcConfig.java) - [SecurityConfig.java](file://crm-auth/src/main/java/com/crm/auth/config/SecurityConfig.java) - [JwtAuthenticationFilter.java](file://crm-auth/src/main/java/com/crm/auth/security/JwtAuthenticationFilter.java) - [PermissionResolverImpl.java](file://crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.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) - [AuthProperties.java](file://crm-auth/src/main/java/com/crm/auth/config/AuthProperties.java) - [LoginUser.java](file://crm-base/src/main/java/com/crm/base/security/LoginUser.java) - [ApiPermissionInterceptorTest.java](file://crm-auth/src/test/java/com/crm/auth/security/ApiPermissionInterceptorTest.java)目录
简介
本文件围绕“动态API权限拦截器”的实现与使用进行系统化说明。该方案通过“资源树(菜单/按钮)→ API URL 模式 → 权限码”的映射,结合进程内缓存与Spring MVC拦截器,实现请求级别的细粒度权限控制。其关键特性包括:
- 基于Ant风格的URL模式匹配,支持单层*与多层**通配
- fail-open策略:未注册规则的路径默认放行
- 进程内Caffeine缓存API权限规则,资源变更时主动失效重建
- 与JWT认证、Spring Security无缝集成,用户权限由登录态解析并注入到上下文
项目结构
本项目采用多模块组织,权限相关逻辑集中在 crm-auth 模块,基础安全能力在 crm-base 模块提供。动态API权限拦截器的关键代码分布如下:
- 拦截器与规则定义:crm-auth/security
- 规则缓存加载:crm-auth/security(Caffeine缓存)
- MVC注册:crm-auth/config/WebMvcConfig
- 安全配置与过滤器链:crm-auth/config/SecurityConfig、crm-auth/security/JwtAuthenticationFilter
- 资源管理与缓存失效:crm-auth/service/impl/ResourceServiceImpl
- 数据模型与枚举:crm-auth/domain/entity/SysMenu、crm-auth/domain/enums/MenuType
graph TB
Client["客户端"] --> SecCfg["SecurityConfig<br/>Spring Security 配置"]
SecCfg --> JwtF["JwtAuthenticationFilter<br/>JWT 校验与权限解析"]
JwtF --> MvCfg["WebMvcConfig<br/>注册拦截器"]
MvCfg --> ApiIntc["ApiPermissionInterceptor<br/>API 权限拦截"]
ApiIntc --> Cache["ApiPermissionCache<br/>Caffeine 缓存规则"]
Cache --> DB["SysMenuMapper<br/>读取按钮节点"]
ApiIntc --> Resp["返回 403/放行"]
图表来源
- SecurityConfig.java:47-67
- JwtAuthenticationFilter.java:32-56
- WebMvcConfig.java:19-24
- ApiPermissionInterceptor.java:37-80
- ApiPermissionCache.java:37-63
章节来源
- SecurityConfig.java:1-69
- WebMvcConfig.java:1-25
核心组件
- ApiPermissionInterceptor:MVC拦截器,负责将请求URI与已注册的API权限规则进行匹配,并结合当前用户权限决定是否放行。
- ApiPermissionRule:一条“URL模式 → 权限码”的规则记录。
- ApiPermissionCache:进程内缓存,从数据库加载按钮类型的菜单节点,生成规则列表;资源变更时主动失效。
- WebMvcConfig:注册拦截器,仅对/api/**路径生效。
- SecurityConfig:Spring Security配置,设置无状态JWT、异常处理、白名单等。
- JwtAuthenticationFilter:JWT校验与权限解析,将用户权限码与角色编码注入到Spring Security上下文。
- PermissionResolverImpl:权限解析引擎,计算数据可见性、权限码并集与角色集合。
- ResourceServiceImpl:资源树CRUD服务,保存/删除后触发权限规则缓存失效。
- SysMenu与MenuType:资源树实体与类型枚举,按钮类型承载perms、apiUrl、status等字段。
章节来源
- ApiPermissionInterceptor.java:20-81
- ApiPermissionRule.java:1-11
- ApiPermissionCache.java:15-64
- WebMvcConfig.java:9-24
- SecurityConfig.java:16-68
- JwtAuthenticationFilter.java:20-66
- PermissionResolverImpl.java:35-139
- ResourceServiceImpl.java:29-269
- SysMenu.java:12-76
- MenuType.java:12-51
架构总览
下图展示了从HTTP请求进入,到JWT校验、权限解析、API权限拦截的完整流程。
sequenceDiagram
participant C as "客户端"
participant S as "SecurityConfig"
participant J as "JwtAuthenticationFilter"
participant P as "PermissionResolverImpl"
participant W as "WebMvcConfig"
participant I as "ApiPermissionInterceptor"
participant K as "ApiPermissionCache"
participant D as "SysMenuMapper"
C->>S : HTTP 请求
S->>J : 进入JWT过滤器
J->>J : 解析Authorization头
J->>P : 解析用户权限(可见性+权限码+角色)
P-->>J : 返回权限结果
J->>J : 构建Authentication并放入上下文
J->>W : 继续过滤器链
W->>I : 进入API权限拦截器
I->>K : 获取规则列表
alt 规则命中
K->>D : 查询按钮节点(启用且含apiUrl)
D-->>K : 规则列表
K-->>I : 规则列表
I->>I : Ant匹配 + 权限码校验
I-->>C : 放行或403
else 无规则命中
I-->>C : 放行(fail-open)
end
图表来源
- SecurityConfig.java:47-67
- JwtAuthenticationFilter.java:32-56
- PermissionResolverImpl.java:52-104
- WebMvcConfig.java:19-24
- ApiPermissionInterceptor.java:37-80
- ApiPermissionCache.java:37-63
详细组件分析
ApiPermissionInterceptor(API权限拦截器)
职责:
- 从缓存获取规则列表,按Ant风格匹配请求URI
- 若未匹配任何规则,则放行(fail-open)
- 若匹配到规则但用户缺少对应权限码,返回403并输出JSON提示
- 使用Spring Security上下文中的用户权限集合进行校验
关键点:
- 使用AntPathMatcher进行模式匹配,支持*和**
- 失败时直接写回响应体,避免进入Controller
flowchart TD
Start(["拦截器入口"]) --> GetRules["获取规则列表"]
GetRules --> Match["Ant匹配请求URI"]
Match --> HasMatch{"是否匹配到规则?"}
HasMatch --> |否| Pass["放行"]
HasMatch --> |是| GetUserAuths["获取用户权限集合"]
GetUserAuths --> CheckPerm{"是否存在任一perms匹配?"}
CheckPerm --> |是| Pass
CheckPerm --> |否| Deny["返回403并写入JSON"]
Pass --> End(["结束"])
Deny --> End
图表来源
- ApiPermissionInterceptor.java:37-80
章节来源
- ApiPermissionInterceptor.java:20-81
ApiPermissionCache(规则缓存)
职责:
- 懒加载并缓存所有启用的按钮节点(包含apiUrl与perms)
- 暴露getRules()供拦截器使用
- 暴露invalidate()供资源管理接口调用以失效缓存
关键点:
- 使用Caffeine单key缓存整份规则列表
- 查询条件:menuType=BUTTON、status=enabled、apiUrl非空
classDiagram
class ApiPermissionCache {
-SysMenuMapper sysMenuMapper
-Cache cache
+getRules() ApiPermissionRule[]
+invalidate() void
-loadRules() ApiPermissionRule[]
}
class SysMenuMapper
class ApiPermissionRule
ApiPermissionCache --> SysMenuMapper : "查询按钮节点"
ApiPermissionCache --> ApiPermissionRule : "生成规则"
图表来源
- ApiPermissionCache.java:25-63
- ApiPermissionRule.java:1-11
章节来源
- ApiPermissionCache.java:15-64
WebMvcConfig(拦截器注册)
职责:
- 将ApiPermissionInterceptor注册到/api/**路径上
- 确保在Spring Security之后、Controller之前执行
章节来源
- WebMvcConfig.java:9-24
SecurityConfig(安全配置)
职责:
- 配置无状态JWT、异常处理器、白名单路径
- 允许业务模块通过配置追加忽略路径
章节来源
- SecurityConfig.java:16-68
- AuthProperties.java:25-54
JwtAuthenticationFilter(JWT过滤器)
职责:
- 解析Authorization头中的Bearer token
- 调用TokenService验证token,获取登录用户信息
- 调用PermissionResolver解析权限,并将结果注入到Spring Security上下文
- 清理DataVisibilityContext以避免跨请求污染
章节来源
- JwtAuthenticationFilter.java:20-66
PermissionResolverImpl(权限解析引擎)
职责:
- 计算数据可见性(部门范围、子树展开)
- 计算权限码并集(仅button类型且status=enabled)
- 计算角色编码集合(带ROLE_前缀归一化)
- 提供可见菜单树构建
章节来源
- PermissionResolverImpl.java:35-139
ResourceServiceImpl(资源管理服务)
职责:
- 提供资源树的增删改查
- 在保存/删除操作后调用ApiPermissionCache.invalidate()使缓存失效
- 图标上传功能(独立于权限逻辑)
章节来源
- ResourceServiceImpl.java:29-269
SysMenu与MenuType(数据模型)
- SysMenu:资源树实体,包含parentId、menuName、menuType、path、component、icon、sort、visible等字段,以及按钮专用字段description、perms、denyBehavior、status、apiUrl
- MenuType:枚举CATALOG/MENU/BUTTON,提供fromCode与isLegalChild方法
章节来源
- SysMenu.java:12-76
- MenuType.java:12-51
依赖关系分析
graph LR
A["ApiPermissionInterceptor"] --> B["ApiPermissionCache"]
B --> C["SysMenuMapper"]
A --> D["Spring Security Context"]
E["JwtAuthenticationFilter"] --> F["PermissionResolverImpl"]
F --> G["SysUserRoleMapper"]
F --> H["SysRoleMapper"]
F --> I["SysRoleMenuMapper"]
F --> J["SysMenuMapper"]
K["ResourceServiceImpl"] --> B
L["WebMvcConfig"] --> A
M["SecurityConfig"] --> E
图表来源
- ApiPermissionInterceptor.java:34-35
- ApiPermissionCache.java:27-32
- JwtAuthenticationFilter.java:29-30
- PermissionResolverImpl.java:44-50
- ResourceServiceImpl.java:40-43
- WebMvcConfig.java:17-17
- SecurityConfig.java:43-45
章节来源
- ApiPermissionInterceptor.java:20-81
- ApiPermissionCache.java:15-64
- JwtAuthenticationFilter.java:20-66
- PermissionResolverImpl.java:35-139
- ResourceServiceImpl.java:29-269
- WebMvcConfig.java:9-24
- SecurityConfig.java:16-68
性能考量
- 进程内缓存:使用Caffeine缓存API权限规则,避免每次请求都查询数据库
- 懒加载:首次访问时才加载规则,后续请求直接从缓存获取
- 主动失效:资源树变更时立即失效缓存,保证一致性
- Ant匹配:使用Spring内置的AntPathMatcher,性能良好
- 权限解析:一次解析获取全部权限信息,减少重复查询
优化建议:
- 根据业务量调整Caffeine缓存参数(最大大小、过期时间等)
- 对于高频访问的API,考虑预热缓存
- 监控缓存命中率,评估是否需要分布式缓存
故障排查指南
常见问题及解决方案:
-
接口返回403无权限
- 检查用户是否拥有对应的权限码
- 确认按钮节点的status是否为enabled
- 验证apiUrl是否与请求URI匹配
-
权限不生效
- 检查资源树是否正确配置
- 确认缓存是否已失效并重新加载
- 查看日志中是否有权限匹配失败的记录
-
缓存不一致
- 确认资源管理接口是否调用了invalidate()
- 检查是否有并发修改资源树的情况
调试技巧:
- 启用DEBUG日志级别查看权限匹配过程
- 使用单元测试验证拦截器行为
- 检查Spring Security上下文中的用户权限
章节来源
- ApiPermissionInterceptorTest.java:20-161
结论
动态API权限拦截器通过“资源树→API规则→权限码”的机制,实现了灵活、高效的细粒度权限控制。结合进程内缓存与Spring Security,既保证了性能又确保了安全性。fail-open策略为未注册的路径提供了默认放行能力,降低了迁移成本。
该方案的优势:
- 配置简单,无需在每个Controller中添加注解
- 支持Ant风格的路径匹配,灵活性高
- 进程内缓存提升性能
- 与现有认证体系无缝集成
适用场景:
- 需要细粒度API权限控制的系统
- 权限规则频繁变更的业务场景
- 对性能有较高要求的应用
附录
配置示例
crm:
auth:
jwt:
secret: your-secret-key-here
ttl-days: 7
ignore-urls:
- /api/public/**
权限码规范
- 格式:模块:功能:操作(如 crm:user:list)
- 唯一性:每个按钮节点必须有唯一的perms
- 命名约定:建议使用小写字母和冒号分隔
最佳实践
- 为每个API端点配置对应的按钮节点
- 定期审查权限配置,清理无用节点
- 使用测试用例验证权限逻辑
- 在生产环境谨慎修改权限配置
章节来源
- AuthProperties.java:25-54
- SysMenu.java:51-71