概述
本节详细分析 PolicyGuard(策略权限守卫)的核心判断逻辑。将前面完成的 Permission Policy 查询、CASL Ability Service 和用户角色策略查询串联起来,形成完整的策略权限判断链路。重点理解"接口要求的策略"与"用户拥有的能力"之间的交叉匹配算法。
PolicyGuard 整体架构
数据查询链路
请求到达 PolicyGuard
│
├── 步骤1:获取路由标识
│ └── Reflector → Handler/Class 装饰器 → Permission Name
│
├── 步骤2:查询接口要求的数据权限
│ └── Permission Name → Permission → Policy 列表(接口要求的策略)
│
├── 步骤3:查询用户拥有的策略
│ └── Request.user → username → User → Role → RolePolicy → Policy 列表
│
├── 步骤4:构建 Ability 实例
│ └── 用户 Policy 列表 → CaslAbilityService → Ability 实例数组
│
└── 步骤5:交叉匹配判断
└── 接口 Policy × 用户 Ability → 最终权限结果
text
关键查询步骤
从接口获取 Permission 标识
// 通过 Reflector 读取装饰器上设置的 Permission 名称
const permissionName = this.reflector.getAllAndOverride<string>(
PERMISSION_KEY,
[context.getHandler(), context.getClass()],
);
typescript
查询接口关联的 Policy
// 通过 Permission 标识查询关联的策略规则
const permissionPolicies = await this.prismaClient.policy.findMany({
where: {
permission: { name: permissionName },
},
});
typescript
查询用户关联的 Policy
// 用户 → 角色 → 角色策略 → 策略
const user = await this.userService.findOne(username);
const rolePolicies = await this.prismaClient.rolePolicy.findMany({
where: {
roleId: { in: user.userRole.map(ur => ur.role.id) },
},
include: { policy: true },
});
typescript
交叉匹配算法
核心思想
两组数据需要交叉判断:
- 左侧:接口要求的 Policy 列表
[P1, P2, P3, P4] - 右侧:用户拥有的 Ability 列表
[A1, A2, A3, A4]
匹配规则
对左侧的每个 Policy Pi:
遍历右侧的所有 Ability Aj:
if Aj.can(Pi) === true
→ Pi 匹配成功,从左侧列表中移除
→ break,进入下一个 Pi
最终判断:
左侧列表为空 → 所有 Policy 都有对应的 Ability → 权限通过 ✅
左侧列表不为空 → 存在没有匹配的 Policy → 权限拒绝 ❌
text
匹配流程图
第一轮:A1 对所有 Policy 判断
┌─────────┬────────┬────────┬────────┐
│ P1 ✓ │ P2 │ P3 │ P4 │
│ (true) │ │ │ │
└─────────┴────────┴────────┴────────┘
↓ 移除 P1
第二轮:A2 对剩余 Policy 判断
┌────────┬────────┐
│ P2 ✓ │ P3 │
│ (true) │ │
└────────┴────────┘
↓ 移除 P2
第三轮:A3 对剩余 Policy 判断
┌────────┐
│ P3 │
│ (false)│
└────────┘
第四轮:A4 对剩余 Policy 判断
┌────────┐
│ P3 ✓ │
│ (true) │
└────────┘
↓ 移除 P3
左侧列表为空 → 权限通过 ✅
text
快速通过场景
A1 判断 P1~P4,全部为 true
→ 左侧列表一次性清空
→ 直接返回 true,无需后续 Ability 判断
text
拒绝场景
A1~A4 全部判断完 P4
→ P4 始终为 false
→ 左侧列表剩余 [P4]
→ 返回 false
text
辅助功能设计
字段信息绑定到 Request
// 将 Policy 中的 fields 信息绑定到 request 上
// 后续业务逻辑可读取这些字段做进一步处理
request.fields = policy.fields;
typescript
这与 AuthGuard 的设计模式一致:Guard 在 request 对象上附加信息,供后续处理使用。
白名单机制
// 角色如果是超级管理员,直接跳过策略判断
if (this.isAdmin(user.roles)) {
return true;
}
typescript
白名单逻辑与 RBAC Guard 中的角色白名单判断类似。
性能考量
| 策略 | 说明 |
|---|---|
| 局部查询优先 | 从路由标识开始,逐步缩小查询范围 |
| 白名单短路 | 超级管理员直接通过,不执行策略判断 |
| 早退出机制 | 左侧 Policy 全部匹配完即返回,不浪费后续 Ability |
| 避免全量查询 | 只查询与当前接口相关的 Policy |
关键知识点总结
| 知识点 | 说明 |
|---|---|
| 查询链路 | 路由标识 → Permission → Policy(接口侧) |
| 用户链路 | username → User → Role → RolePolicy → Policy(用户侧) |
| 交叉匹配 | 接口 Policy 列表 vs 用户 Ability 列表,逐一匹配 |
| 移除策略 | 匹配成功的 Policy 从待验证列表中移除 |
| 早退出 | 左侧列表清空即可返回 true |
| 字段绑定 | Policy fields 绑定到 request 供后续使用 |
↑