策略组合算法:Deny-overrides、Permit-overrides 等归纳总结
好,咱们今天聊聊策略组合算法。很多刚接触 ABAC 的同学都会问一句话:「如果一个策略允许了,另一个策略拒绝了,到底听谁的?」 这个问题,恰恰就是策略组合算法要解决的。
我在项目中见过不少团队,策略写得洋洋洒洒几十条,结果一上线发现有的请求被误拒,有的不该放的却放了。一查原因,全都是因为没搞懂策略组合的优先级。说白了,策略组合算法就是 ABAC 引擎里的交通规则——谁先走、谁让行,必须清清楚楚。
为什么需要策略组合算法?
一个 ABAC 策略集通常包含多条策略,每条策略都有自己的 Target、Condition 和 Effect(Permit 或 Deny)。当一条请求命中多个策略时,矛盾就出现了。策略组合算法定义了这些矛盾的裁决逻辑。
常见的组合算法有五种:
- Deny-overrides(拒绝优先):只要有一条策略裁决为 Deny,最终结果就是 Deny。
- Permit-overrides(允许优先):只要有一条策略裁决为 Permit,最终结果就是 Permit。
- First-applicable(第一个适用):按顺序找到第一条命中的策略,取它的 Effect。
- Only-one-applicable(唯一适用):如果命中的策略多于一条,直接返回 Indeterminate(不确定)。
- Ordered-deny-overrides / Ordered-permit-overrides:带顺序的拒绝优先/允许优先。
Deny-overrides:我最常用的算法
我个人习惯在高安全场景下使用 Deny-overrides。比如金融系统的转账接口,任何一条策略只要认为「该操作有风险」,哪怕其他十条策略都说可以,我也必须拒绝。为什么?因为安全是木桶效应,最短的那块板决定了结果。
我曾在一个银行项目中遇到过这样的坑:我们把用户隐私策略和业务策略写在了同一个策略集里,用了默认的 Permit-overrides。结果某次运维误操作,把一条「禁止外网访问数据库」的策略加到了业务策略里,但因为 Permit-overrides,所有外网请求居然通过了……还好是在测试环境发现的。从那以后,凡是涉及安全、合规的策略集,我全部强制换成 Deny-overrides。
Permit-overrides:适合开放场景
Permit-overrides 正好反过来。只要有一条策略说「可以」,就允许。 这种算法适合什么场景?比如企业内部的门禁系统,只要你有 VIP 卡、临时访客码、或者领导的授权短信,任何一种方式验证通过,就可以进门。你想想看,如果这时候用 Deny-overrides,那门禁就太死板了。
我曾经在做一个 SaaS 平台时,租户管理员可以自定义权限策略。默认我们用了 Permit-overrides,结果有个租户不小心写了一条「允许所有操作」的宽泛策略,和别的策略混在一起,瞬间把整个租户的权限放开了。嗯,后来我们加了策略有效性检查,并且在租户级别强制使用 Deny-overrides 兜底。
First-applicable:顺序即优先级
First-applicable 算法最常见于 XACML 的策略组合中。策略列表是有序的,引擎从上到下依次匹配,匹配到第一条就返回其结果。 这样设计有一个好处:你可以把最具体的策略放在前面,通用的兜底策略放在最后。
举个例子:
策略1: 如果角色是 "admin" → Permit
策略2: 如果角色是 "user" 且操作是 "read" → Permit
策略3: 否则不论什么情况 → Deny
用 First-applicable,管理员直接放行;普通用户只能读;其他任何操作都被 Deny 兜底。逻辑很清晰,对吧?
Only-one-applicable:强制唯一命中
这个算法比较特殊,它要求对每条请求最多只能有一条策略命中。如果命中多条,引擎直接返回 Indeterminate(不确定)。你会问,这不是有点极端吗?其实它在一些需要强制互斥的场景下很有用,比如「你不能既是内部员工又是外部顾问」的权限设计。
Ordered-deny-overrides 与 Ordered-permit-overrides
这两个是带顺序的变体。普通 Deny-overrides 和 Permit-overrides 在遇到多个冲突结果时,通常会返回 Indeterminate(因为无法确定谁先谁后)。而带顺序的版本会按照策略列表的顺序来决定优先级。例如 Ordered-deny-overrides 遇到多个 Deny 时,取列表中第一个 Deny 的结果;如果没有 Deny,则按列表顺序取第一个 Permit。
我个人觉得这个算法有点「四不像」,既不像纯 Deny-overrides 那么,也不像 First-applicable 那么干脆。但我在 IAM 厂商的 SDK 里见过它的身影,用来处理不同管理员添加的策略之间的冲突。
算法对比表格
| 算法名称 | 优先结果 | 多策略冲突 | 适用场景 |
|---|---|---|---|
| Deny-overrides | Deny | Deny 胜出 | 安全审计、合规检查 |
| Permit-overrides | Permit | Permit 胜出 | 开放合作、应急放行 |
| First-applicable | 按顺序 | 取首个命中 | 细粒度分层控制 |
| Only-one-applicable | 唯一命中 | 返回 Indeterminate | 互斥角色、严格分组 |
| Ordered-deny-overrides | 顺序+Deny优先 | 按顺序取首次 | 多管理源的策略合并 |
SVG:策略组合算法决策流程
下面这张图展示了从请求命中多条策略到最终裁决的全过程,不同算法走不同分支。
避坑指南:我曾经踩过的坑
- 策略合并时忘记指定算法: 大多数 ABAC 引擎默认使用 Deny-overrides 或 Permit-overrides(取决于产品)。我遇到过一个团队直接用了 XACML 的默认值,结果发现有的场景下 Deny 没有优先,白白漏掉了安全规则。建议每个策略集都显式声明组合算法。
- Indeterminate 处理不当: 当算法无法裁决时(如 Only-one-applicable 遇到多条命中),引擎返回 Indeterminate。很多开发者直接忽略这个结果,相当于放行了请求。我在审计时发现过一次,赶紧补上了「Indeterminate 视为 Deny」的兜底逻辑。
- Ordered 算法与策略顺序的耦合: 如果你用了 Ordered 变体,那么策略的排序就决定了最终结果。有时候运维人员在 UI 上拖拽调整策略顺序,自己却没察觉,导致权限漂移。我的建议是:给策略加一个显式的 priority 字段,不要依赖数组下标。
如何选择?我的个人经验判断
我给几个简单的判断标准:
- 如果整个系统的最高原则是安全(比如支付、医疗数据),无脑选 Deny-overrides。
- 如果用户体验优先,而且你有严格的策略审核机制,可以考虑 Permit-overrides。
- 如果策略是分层细粒度控制(比如不同角色、不同资源),First-applicable 是最清晰的。
- 如果策略集来源不同管理域(如租户 + 平台),且你想避免冲突,Only-one-applicable 是一个强制隔离手段。