策略结构:策略的组成(Effect、Condition、Target)

策略是ABAC规则引擎的核心执行单元。说白了,它就是一条「如果…那么…」的逻辑判断。我在做第一个ABAC项目时,把策略想得太复杂了,结果写出来的策略又臭又长。后来才明白,一个完整的策略其实就三个部分:Target(目标)、Condition(条件)、Effect(效果)。

嗯,咱们一个一个拆开看。

4.1 策略的三要素

先给个直观的对比。你想想看,策略就像一道门禁规则:

要素 门禁类比 ABAC中的含义
Target 这道门管谁?管哪个区域? 策略适用的资源、主体、动作范围
Condition 进门需要什么条件? 基于属性的布尔逻辑判断
Effect 允许进还是禁止进? Permit(允许)或 Deny(拒绝)

我个人习惯把策略写成这样:

策略 = Target + Condition → Effect

Target先过滤掉不相关的请求,Condition做精细判断,Effect给出最终结论。顺序不能乱。

4.2 Target:策略的「门面」

Target的作用是快速判断「这条策略跟当前请求有没有关系」。我在项目中遇到过一种情况:系统里有上千条策略,每次请求都要遍历一遍,性能惨不忍睹。后来加了Target预过滤,效率直接翻倍。

一个典型的Target包含三个维度:

  • 主体(Subject):谁在操作?比如用户角色、部门、安全等级。
  • 资源(Resource):操作什么?比如文档类型、API路径、数据分类。
  • 动作(Action):做什么?比如读取、写入、删除、审批。

举个例子:

{
  "target": {
    "subject": { "role": "manager" },
    "resource": { "type": "financial_report" },
    "action": { "name": "read" }
  }
}

这条Target的意思是:只有「经理」角色对「财务报表」的「读取」操作,才可能匹配这条策略。其他请求直接跳过,不用往下走了。

我的经验:Target里的属性尽量用枚举值,别用模糊匹配。枚举值可以建索引,查询快得多。我曾经用通配符写Target,结果每次匹配都要做字符串正则,线上CPU直接飙到80%。

4.3 Condition:策略的「大脑」

Condition是策略里最灵活的部分。它用布尔表达式对属性做精细判断。Target只是粗筛,Condition才是真正的决策逻辑。

Condition支持的操作符我列一下:

操作符 说明 示例
equals 等于 user.department == "finance"
notEquals 不等于 user.level != "guest"
greaterThan 大于 resource.amount > 10000
lessThan 小于 resource.amount < 50000
in 属于集合 user.role in ["admin","manager"]
contains 包含 resource.tags contains "confidential"
stringEquals 字符串精确匹配 resource.region == "cn-north"
dateTimeGreaterThan 时间比较 currentTime > "2025-01-01T00:00:00Z"

Condition可以组合使用。我常用的组合方式有两种:

  • AllOf(与):所有子条件必须同时满足。
  • AnyOf(或):任意一个子条件满足即可。

看个完整的例子:

{
  "condition": {
    "allOf": [
      { "equals": { "user.department": "finance" } },
      { "greaterThan": { "resource.amount": 10000 } },
      { "anyOf": [
        { "equals": { "user.level": "senior" } },
        { "equals": { "user.role": "finance_manager" } }
      ]}
    ]
  }
}

这条Condition的意思是:用户必须是财务部的,操作的金额要大于1万,而且用户要么是高级员工,要么是财务经理。三个条件缺一不可。

避坑指南:我曾经把Condition嵌套了七八层,结果调试的时候自己都看不懂。后来定了个规矩:Condition嵌套不超过3层。超过3层就拆成子策略,或者用策略组合来解决。

4.4 Effect:策略的「结论」

Effect只有两个值:Permit 和 Deny。简单吧?但这里有个坑——Deny优先。

什么意思呢?如果多条策略匹配了同一个请求,其中一条是Deny,其他都是Permit,那最终结果是Deny。这是ABAC的通用安全原则:宁可错杀,不可放过。

我习惯把Effect放在策略的最外层,一眼就能看到结论:

{
  "effect": "Deny",
  "target": { ... },
  "condition": { ... }
}

嗯,这里要注意:Effect不能为空。如果一条策略没有Effect,引擎应该直接报错,不能默认Permit或Deny。我在设计引擎时,把Effect校验放在了策略加载阶段,而不是运行时。这样有问题能早点发现。

4.5 完整策略示例

把上面三部分拼起来,就是一个完整的策略:

{
  "policyId": "POLICY-001",
  "description": "财务经理可审批金额超过10万的报销单",
  "effect": "Permit",
  "target": {
    "subject": { "role": "finance_manager" },
    "resource": { "type": "reimbursement" },
    "action": { "name": "approve" }
  },
  "condition": {
    "allOf": [
      { "greaterThan": { "resource.amount": 100000 } },
      { "equals": { "resource.status": "pending" } },
      { "dateTimeLessThan": { "resource.submitTime": "2025-12-31T23:59:59Z" } }
    ]
  }
}

这条策略的逻辑是:

  1. Target先过滤:只有财务经理对报销单的审批操作才进入判断。
  2. Condition再判断:金额大于10万、状态是待审批、提交时间在2025年之前。
  3. 都满足的话,Effect给出Permit。
核心要点:Target是「能不能谈」,Condition是「谈不谈得拢」,Effect是「最终拍板」。三个环节各司其职,别混在一起写。

4.6 策略的编写规范

我在团队里推行了几条硬性规范,分享给你:

  • 一条策略只做一件事。别想着一条策略覆盖所有场景,拆开写更清晰。
  • Effect写在最前面。读策略的人第一眼就知道是允许还是拒绝。
  • Target尽量精确。Target越精确,策略匹配越快,引擎压力越小。
  • Condition里不要写死值。比如金额阈值、时间范围,应该做成配置参数,方便后期调整。
  • 每条策略必须有description。半年后你回头看自己写的策略,没有注释你会想骂人。

下面这张图展示了策略的完整执行流程:

策略执行流程 接入请求 Target 匹配 主体 / 资源 / 动作 是否匹配? 匹配? (Target) 跳过策略 Condition 判断 满足? (Condition) 返回 Deny 返回 Effect 结果 Target不匹配 → 跳过策略;Condition不满足 → 返回Deny;都满足 → 返回Effect

从流程图能看出来,Target是第一个关卡。它不匹配的话,Condition根本不会执行。这种设计能大幅减少不必要的计算。我在生产环境做过压测,加了Target预过滤后,策略匹配的QPS提升了将近3倍。

4.7 策略的版本管理

最后聊个实战问题。策略写好了,怎么管理?

我建议给每条策略加版本号和生效时间:

{
  "policyId": "POLICY-001",
  "version": "2.1.0",
  "effectiveFrom": "2025-01-01T00:00:00Z",
  "effectiveUntil": "2025-12-31T23:59:59Z",
  "effect": "Permit",
  "target": { ... },
  "condition": { ... }
}

为什么要加版本?因为策略会变。你改了一条策略,结果线上出问题了,想回退却发现找不到旧版本——这种事我经历过两次,后来老老实实加了版本管理。

嗯,策略结构就这些。Target、Condition、Effect,三个部分缺一不可。写策略的时候,先想清楚Target范围,再设计Condition逻辑,最后定Effect结论。顺序对了,策略就不会乱。