4. ABAC 模型详解:基于属性的访问控制
好,咱们进入第四章。前面聊了 RBAC,很多同学在实际落地时发现——角色越建越多,权限越分越细,最后角色数量比用户还多。这其实是个信号:你的场景已经复杂到 RBAC 有点撑不住了。
这时候就该 ABAC 上场了。基于属性的访问控制,说白了就是——能不能访问,不看你是什么角色,而看你、资源、环境这三方面凑在一起是什么情况。我最早接触 ABAC 是在一个跨国 SaaS 项目里,不同国家的合规要求完全不同,RBAC 根本没法搞,最后全靠 ABAC 兜底。
4.1 什么是 ABAC?
ABAC 的全称是 Attribute-Based Access Control。它的核心思想很简单:用一组属性来描述请求的上下文,然后根据预定义的策略来判断是否允许。
举个例子你就明白了:
- RBAC 的思路:你是“管理员”,所以你能删除文档。
- ABAC 的思路:你是“文档的创建者”,并且“文档状态是草稿”,并且“当前时间在工作时间内”,所以你才能删除。
看到了吗?ABAC 把判断条件拆成了多个属性维度。每个维度都可以单独定义,组合起来就是一条策略。这种灵活性,是 RBAC 给不了的。
核心区别一句话总结:RBAC 问“你是谁”,ABAC 问“你、资源、环境现在是什么情况”。
4.2 ABAC 的核心三要素
ABAC 里所有策略都围绕三个主体展开:主体(Subject)、资源(Resource)、环境(Environment)。我习惯把它们叫做“ABAC 三角”。
4.2.1 主体属性
主体就是“谁”在发起请求。可以是用户、服务账号、设备等。主体属性包括:
- 身份属性:用户 ID、部门、职级、角色列表
- 安全属性:安全等级、认证方式(密码 / MFA / SSO)
- 行为属性:登录 IP、设备指纹、历史行为评分
我在一个金融项目里遇到过一个问题:同一个用户,从内网访问和从 VPN 访问,权限应该不一样。这就是典型的主体属性差异。我们当时在策略里加了一条——subject.network_zone == "internal",问题就解决了。
4.2.2 资源属性
资源是“对什么”做操作。资源属性描述的是被访问对象本身:
- 分类属性:文档类型、数据敏感级别、所属项目
- 状态属性:文档状态(草稿 / 已发布 / 已归档)、是否加锁
- 关系属性:创建者、最后修改者、所属部门
举个例子:一个文档,只有“创建者”和“审核人”能编辑,其他人只能读。这个策略用 RBAC 得建两个角色,用 ABAC 只需要一条规则——resource.owner == subject.id OR resource.reviewer == subject.id。
4.2.3 环境属性
环境是“在什么条件下”操作。这个维度经常被忽略,但恰恰是 ABAC 最值钱的地方:
- 时间属性:当前时间、工作日 / 节假日、时间段
- 位置属性:IP 段、物理位置、国家
- 风险属性:当前系统负载、安全告警级别、网络环境
嗯,这里要注意:环境属性是动态的,每次请求都可能不一样。所以策略引擎必须实时获取这些值,不能缓存。
我的经验:环境属性最容易出 bug。我曾经遇到一个 case——策略里写了 environment.time >= "09:00" AND environment.time <= "18:00",结果跨时区的用户全被拒了。后来我们统一用 UTC+0 做策略计算,前端展示时再转本地时间。
4.3 ABAC 策略引擎设计
策略引擎是 ABAC 的大脑。它的工作流程大致是这样的:
- 接收请求:拿到主体、资源、操作、环境信息
- 属性收集:从多个数据源拉取属性值
- 策略匹配:找到所有适用的策略
- 策略评估:逐条计算,得出 Permit / Deny / NotApplicable
- 决策返回:返回最终结果,附带评估日志
下面这张图是我常用的策略引擎架构,你可以参考一下:
策略引擎的实现,我建议用 策略组合算法。常见的算法有:
- Deny Overrides:只要有一条策略 Deny,最终结果就是 Deny。适合安全优先的场景。
- Permit Overrides:只要有一条策略 Permit,最终结果就是 Permit。适合开放优先的场景。
- First Applicable:按优先级顺序,匹配到第一条就返回。性能最好,但策略顺序很重要。
避坑指南:我曾经在一个项目中用了 Permit Overrides,结果一条宽松策略覆盖了所有严格策略,导致敏感数据泄露。后来改成 Deny Overrides + 显式 Deny 策略,才把问题堵住。安全场景下,默认 Deny + 显式 Permit 是最稳妥的做法。
4.4 ABAC 与 RBAC 的对比
很多同学问我:ABAC 是不是要取代 RBAC?我的答案是——不是取代,是互补。下面这张表可以帮你快速理解它们的区别:
| 对比维度 | RBAC | ABAC |
|---|---|---|
| 授权粒度 | 粗粒度(角色级别) | 细粒度(属性级别) |
| 策略表达 | 角色-权限映射 | 属性-条件表达式 |
| 动态性 | 静态(角色变更需管理员操作) | 动态(属性实时计算) |
| 管理复杂度 | 低(角色数量可控时) | 高(属性多、策略多时) |
| 适用场景 | 组织架构稳定、权限变化少 | 多维度条件、动态环境、合规要求复杂 |
| 性能开销 | 低(缓存友好) | 中高(实时属性拉取 + 策略计算) |
| 审计追溯 | 容易(角色变更记录) | 较复杂(需记录属性快照) |
说说我的实践感受。RBAC 适合做基础权限骨架,ABAC 适合做精细化策略覆盖。我常用的模式是:
- 先用 RBAC 定义大方向——比如“财务部员工可以访问财务系统”
- 再用 ABAC 做细粒度控制——比如“只有财务经理和项目负责人可以导出报表,且只能在办公时间操作”
这种混合模式,既保留了 RBAC 的管理简洁性,又获得了 ABAC 的灵活性。说白了,别把 ABAC 当成 RBAC 的替代品,把它当成 RBAC 的增强插件。
我的建议:如果你的系统角色数量超过 50 个,或者权限规则里出现了“如果…那么…除非…”这样的逻辑,赶紧考虑引入 ABAC。别等到角色爆炸了再重构,那代价太大了。
4.5 一个简单的 ABAC 策略示例
最后,给你看一个真实的策略定义。我们用 JSON 格式来表达一条 ABAC 策略:
{
"policyId": "POL-001",
"effect": "Permit",
"target": {
"subject": {
"department": "Engineering",
"securityLevel": {"gte": 3}
},
"resource": {
"type": "source_code",
"project": "project-alpha"
},
"action": ["read", "checkout"],
"environment": {
"time": {"between": ["09:00", "18:00"]},
"network": "vpn"
}
},
"condition": {
"allOf": [
{"subject.region": "CN"},
{"resource.classification": {"neq": "top_secret"}}
]
}
}
这条策略的意思是:Engineering 部门、安全等级 3 以上的员工,可以在工作时间通过 VPN 访问 project-alpha 的源代码,但不能访问绝密级别的代码,且仅限中国区。
你看,一条策略就覆盖了主体、资源、环境三个维度的条件。如果用 RBAC,你得建多少个角色才能表达清楚?