4. 基于属性的访问控制(ABAC):属性定义、策略引擎与语言实践
上一章我们聊了 RBAC,它把权限管理简化成了「角色-权限」的映射。但说实话,在实际项目中,RBAC 有时候会显得不够灵活。比如,你只允许「高级工程师」在「工作时间」访问「生产环境」的「核心数据库」。你看,这里涉及了用户身份、时间、环境、资源类型等多个维度。RBAC 处理这种场景,要么得创建大量细粒度角色,要么就得在代码里写死逻辑。
这就是 ABAC 登场的地方。ABAC 的核心思想是:用属性来描述一切,用策略来决定授权。我个人习惯把 ABAC 看作是权限控制的「规则引擎」,它比 RBAC 更动态、更精细。
4.1 属性的三要素:主体、资源、环境
ABAC 里的「属性」是啥?说白了,就是描述一个对象特征的键值对。我把它分成三大类,你在设计时一个都不能少:
- 主体属性:谁要访问?比如用户 ID、部门、角色、职级、安全等级、是否通过 MFA 认证。
- 资源属性:要访问什么?比如资源类型、所属项目、数据分类(机密/内部)、地理位置、创建者。
- 环境属性:在什么条件下访问?比如当前时间、访问来源 IP、设备类型、网络环境(内网/外网)、风险等级。
关键点:属性来源必须可信。主体属性通常来自 IDP(身份提供商),资源属性来自 CMDB 或资源管理系统,环境属性来自上下文感知模块。我在项目中遇到过因为属性源数据不一致导致的权限绕过问题,后来强制要求所有属性必须经过签名验证。
举个例子,一条典型的 ABAC 策略可能是这样的:
允许操作:当且仅当
主体.部门 == "研发部" AND
主体.职级 >= "P7" AND
资源.数据分类 == "内部" AND
环境.时间 BETWEEN "09:00" AND "18:00" AND
环境.网络 == "公司VPN"
你看,这比 RBAC 的「研发部经理角色可以读取所有文档」要精确得多。
4.2 策略引擎设计:从请求到决策
策略引擎是 ABAC 的大脑。它的工作流程其实不复杂,我画了一张图帮你理解:
流程其实就五步:
- PEP(策略执行点) 拦截到访问请求。
- 上下文处理器 去拉取主体、资源、环境的所有属性。
- 把这些属性打包成 XACML 请求上下文,发给 PDP。
- PDP(策略决策点) 拿着属性去匹配策略集,算出结果。
- 结果返回给 PEP,由它执行「允许」或「拒绝」。
我的经验:PDP 的性能是关键。如果策略集很大(比如上千条),每次请求都全量匹配会很慢。我建议在 PDP 前面加一层策略缓存,或者用策略索引树来加速。曾经有个项目,PDP 响应时间从 5ms 飙升到 200ms,最后发现是策略里用了太多正则表达式,改成精确匹配后问题解决。
4.3 XACML 与 ALFA 语言
说到 ABAC,就绕不开 XACML。它是 OASIS 制定的标准,定义了策略的语言格式和架构。但说实话,XACML 的 XML 写起来非常啰嗦。你看一个简单的策略:
<Policy PolicyId="example" RuleCombiningAlg="deny-overrides">
<Target>
<Subjects><Subject><SubjectMatch MatchId="string-equal">
<AttributeValue DataType="string">engineering</AttributeValue>
<SubjectAttributeDesignator AttributeId="department"/>
</SubjectMatch></Subject></Subjects>
</Target>
<Rule RuleId="allow-read" Effect="Permit">
<Condition>
<Apply FunctionId="time-in-range">
<AttributeValue DataType="time">09:00:00</AttributeValue>
<AttributeValue DataType="time">18:00:00</AttributeValue>
<EnvironmentAttributeDesignator AttributeId="current-time"/>
</Apply>
</Condition>
</Rule>
</Policy>
嗯,这还只是最简单的策略。要是策略复杂起来,XML 嵌套能让你看到怀疑人生。所以我个人更推荐用 ALFA(Axiomatics Language for Authorization)。它是 XACML 的轻量级 DSL,写起来像自然语言:
namespace example {
policy allowEngineeringAccess {
target clause subject.department == "engineering"
apply denyOverrides
rule allowBusinessHours {
target clause environment.currentTime in ["09:00:00", "18:00:00"]
permit
}
rule denyAfterHours {
target clause not environment.currentTime in ["09:00:00", "18:00:00"]
deny
}
}
}
你看,ALFA 把策略的意图表达得非常清晰。它最终会被编译成标准的 XACML XML,但开发人员只需要维护 ALFA 源码就行。
| 特性 | XACML | ALFA |
|---|---|---|
| 语法 | XML,冗长 | DSL,简洁 |
| 可读性 | 差,嵌套深 | 好,接近自然语言 |
| 学习曲线 | 陡峭 | 平缓 |
| 工具支持 | 广泛 | 需编译为 XACML |
| 适用场景 | 系统间交换 | 人工编写策略 |
避坑指南:我曾经在一个项目中直接用 XACML 写策略,结果策略文件超过 5000 行,维护成本极高。后来全部迁移到 ALFA,策略量减少了 60%,而且业务人员也能看懂。如果你从零开始,我建议直接用 ALFA,别走弯路。
4.4 ABAC 与 RBAC 的融合:取长补短
你可能会问:有了 ABAC,是不是就可以淘汰 RBAC 了?我的答案是:千万别。ABAC 虽然灵活,但策略管理成本高;RBAC 虽然粗粒度,但简单直观。实际生产环境中,两者通常是融合使用的。
我常用的融合模式是:RBAC 做粗粒度授权,ABAC 做细粒度过滤。
举个例子:
- 先用 RBAC 判断「用户是否属于『财务部经理』角色」—— 这是第一道门。
- 再用 ABAC 判断「当前时间是否在财务月结期间」以及「用户是否在指定 IP 段内」—— 这是第二道锁。
这样设计的好处是:
- 性能优化:大部分请求在 RBAC 层就被拒绝了,不需要进入 ABAC 策略引擎。
- 管理简化:日常的角色分配用 RBAC,特殊的动态约束用 ABAC。
- 审计清晰:RBAC 负责「谁有什么权限」,ABAC 负责「在什么条件下能用」。出了问题,定位责任非常快。
融合架构的关键点:在 PEP 层做两级判断。先查 RBAC 的角色-权限映射表,如果通过,再调用 ABAC 的 PDP 做属性级校验。我见过一些团队把 RBAC 和 ABAC 混在一起写策略,结果逻辑混乱,出了安全事件都说不清是哪个环节的问题。
最后说一句,ABAC 不是银弹。如果你的系统权限模型很简单(比如只有管理员和普通用户),用 ABAC 就是杀鸡用牛刀。但如果你面对的是多云环境、微服务架构、或者需要满足 SOX、GDPR 等合规要求,ABAC + RBAC 的融合方案几乎是必选项。