策略权限守卫:验证函数 & Mongo 复杂查询
上一节完成了基础 action/subject 和静态 conditions 的验证,但静态条件无法满足动态场景(如"用户只能更新自己的数据")。本节验证 CaslAbilityService 的三种处理方法:handleFunctionType(基于函数的策略)和 handleMongoType(MongoDB 查询操作符),完整打通条件匹配的所有路径。
三种条件策略的定位
| 类型 | type 值 | 适用场景 | 条件存储格式 |
|---|---|---|---|
| JSON 基础条件 | 0 | 简单属性匹配 | {"username": "tom1"} |
| MongoDB 查询 | 1 | 复杂多字段逻辑 | {"$or": [...]} |
| 函数条件 | 2 | 动态绑定运行时变量 | "username === user.username" |
函数条件的验证
配置函数类型 Policy
函数类型 Policy 需要同时提供 conditions(函数体字符串)和 args(参数定义):
// PATCH /roles/:id - 更新角色的策略权限
{
"permissions": [
{
"type": 2,
"effect": "can",
"action": "update",
"subject": "user",
"conditions": "username === user.username",
"args": ["user"]
}
]
}
json
注意:
args必须放在 policy 的外层,而非嵌套在conditions对象内部。这是常见的数据结构错误。
handleFunctionType 的执行流程
// CaslAbilityService - handleFunctionType
handleFunctionType(policy: IPolicy, args: any) {
const { can, cannot, build } = new AbilityBuilder(PureAbility);
// 1. 从 policy.args 构建函数参数
let arr: string[] = [];
if (typeof policy.args === 'string') {
arr = policy.args.split(',');
}
// 2. 使用 new Function 动态创建函数
const fn = new Function(...arr, `return ${policy.conditions}`);
// 3. 使用 PureAbility + MatchConditions 处理函数条件
const conditionsMatcher: MatchConditions = match => match;
const action = this.determineAction(policy.effect, can, cannot);
action(policy.action, policy.subject, (subjectObj) => {
return fn(...args); // 将运行时参数传递给动态函数
});
return build({ conditionsMatcher });
}
typescript
关键:new Function 与参数传递
new Function 动态创建函数的过程等价于:
// 数据库存储: args = "user", conditions = "username === user.username"
// 还原后的函数等价于:
const dynamicFn = function(user) {
return username === user.username;
};
// 在 ability 判断时,subject 实例的属性会被传入匹配
// subjectObj.username === user.username
typescript
调试时可以在条件函数内加入 console.log 验证参数传递:
// 在 conditions 字符串中加入调试输出
"console.log(user); return username === user.username"
typescript
当 subjectInstance.username 与 args[0].username 匹配时,permissionGranted 为 true;不匹配时为 false。
MongoDB 查询条件的验证
配置 MongoDB 类型 Policy
使用 MongoDB 查询操作符定义复杂条件:
{
"type": 1,
"effect": "can",
"action": "update",
"subject": "user",
"conditions": {
"type": "object",
"data": {
"username": { "$in": ["tom1", "tom2"] }
}
}
}
json
handleMongoType 的执行流程
// CaslAbilityService - handleMongoType
handleMongoType(policy: IPolicy) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// 从 buildMongoQueryMatcher 工厂创建完整的条件匹配器
const conditionsMatcher = buildMongoQueryMatcher(
allParsingInstructions,
allInterpreters
);
const action = this.determineAction(policy.effect, can, cannot);
// conditions 可能需要从 JSON 字符串解析
let conditions = policy.conditions;
if (typeof conditions === 'string') {
conditions = JSON.parse(conditions);
}
// 取嵌套的 data 字段
if (policy.conditions?.data) {
conditions = policy.conditions.data;
}
action(policy.action, policy.subject, conditions);
return build({ conditionsMatcher });
}
typescript
条件匹配验证
// Policy 条件: { username: { $in: ["tom1", "tom2"] } }
// subject 实例: { username: "tom1" }
ability.can('update', subjectInstance);
// username "tom1" 在 $in 数组中 => true
// subject 实例: { username: "other" }
ability.can('update', subjectInstance);
// username "other" 不在 $in 数组中 => false
typescript
Conditions 数据结构的处理逻辑
在 PolicyGuard 中需要根据 type 和 conditions 的结构正确提取数据:
// PolicyGuard 中 conditions 数据提取逻辑
let conditions;
if (policy.conditions) {
if (policy.conditions.type === 'function' && policy.conditions.data) {
// 函数类型:取 data 字符串
conditions = policy.conditions.data;
} else if (policy.conditions.data) {
// MongoDB 类型:取 data 对象
conditions = policy.conditions.data;
} else {
// 基础类型:直接使用 conditions 对象
conditions = policy.conditions;
}
}
typescript
三种处理方法的验证总结
CaslAbilityService 创建 ability 实例的三种路径
│
├─ handleJsonType (type=0)
│ createMongoAbility + 直接 conditions 对象
│ 适用: { username: "tom1" }
│
├─ handleMongoType (type=1)
│ createMongoAbility + buildMongoQueryMatcher
│ 适用: { $or: [{...}, {...}] }, { $in: [...] }
│
└─ handleFunctionType (type=2)
PureAbility + new Function + args 参数
适用: 动态条件绑定当前用户/运行时变量
text
至此,CaslAbilityService 的三个创建 ability 实例的方法全部验证通过,覆盖了从简单属性匹配到复杂查询操作符再到动态函数绑定的完整条件匹配链路。
↑