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 的大脑。它的工作流程大致是这样的:

  1. 接收请求:拿到主体、资源、操作、环境信息
  2. 属性收集:从多个数据源拉取属性值
  3. 策略匹配:找到所有适用的策略
  4. 策略评估:逐条计算,得出 Permit / Deny / NotApplicable
  5. 决策返回:返回最终结果,附带评估日志

下面这张图是我常用的策略引擎架构,你可以参考一下:

ABAC 策略引擎核心流程 访问请求 属性收集器 主体属性 | 资源属性 | 环境属性 策略匹配器 策略评估引擎 决策输出 策略存储库 属性数据源 LDAP / DB / API 说明:属性收集器从多个数据源拉取实时属性,策略匹配器根据请求找到适用策略,评估引擎逐条计算后输出决策

策略引擎的实现,我建议用 策略组合算法。常见的算法有:

  • 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,你得建多少个角色才能表达清楚?