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 的大脑。它的工作流程其实不复杂,我画了一张图帮你理解:

ABAC 策略引擎决策流程 ABAC 策略引擎决策流程 1. 访问请求 2. 策略执行点(PEP) 3. 上下文处理器(属性收集) 主体属性 资源/环境属性 4. 策略决策点(PDP) 5. 允许/拒绝/不适用

流程其实就五步:

  1. PEP(策略执行点) 拦截到访问请求。
  2. 上下文处理器 去拉取主体、资源、环境的所有属性。
  3. 把这些属性打包成 XACML 请求上下文,发给 PDP。
  4. PDP(策略决策点) 拿着属性去匹配策略集,算出结果。
  5. 结果返回给 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 段内」—— 这是第二道锁。

这样设计的好处是:

  1. 性能优化:大部分请求在 RBAC 层就被拒绝了,不需要进入 ABAC 策略引擎。
  2. 管理简化:日常的角色分配用 RBAC,特殊的动态约束用 ABAC。
  3. 审计清晰:RBAC 负责「谁有什么权限」,ABAC 负责「在什么条件下能用」。出了问题,定位责任非常快。

融合架构的关键点:在 PEP 层做两级判断。先查 RBAC 的角色-权限映射表,如果通过,再调用 ABAC 的 PDP 做属性级校验。我见过一些团队把 RBAC 和 ABAC 混在一起写策略,结果逻辑混乱,出了安全事件都说不清是哪个环节的问题。

最后说一句,ABAC 不是银弹。如果你的系统权限模型很简单(比如只有管理员和普通用户),用 ABAC 就是杀鸡用牛刀。但如果你面对的是多云环境、微服务架构、或者需要满足 SOX、GDPR 等合规要求,ABAC + RBAC 的融合方案几乎是必选项。