策略语言与语法:从XACML到JSON,再到组合算法

策略语言,说白了就是ABAC系统的“法律条文”。你定义了属性,设计了模型,最后总得有个东西把这些规则写下来吧?这就是策略语言干的事。

这一章我们聊三个主流表达方式:XACML标准、ALFA语言、JSON策略表示法。最后再讲讲策略组合算法——当多条规则冲突时,到底听谁的。

XACML标准:老祖宗级别的规范

XACML(eXtensible Access Control Markup Language)是OASIS制定的标准。我最早接触ABAC时,啃的就是XACML 3.0的规范文档。说实话,那文档写得真叫一个“全面”,但也真叫一个“啰嗦”。

XACML的核心结构,我习惯用三个要素来记:

  • PolicySet:策略集,可以嵌套其他PolicySet或Policy
  • Policy:策略,包含一条或多条Rule
  • Rule:规则,最细粒度的授权单元

每个Rule都有三个关键部分:

  1. Target:这条规则适用于谁、什么资源、什么动作
  2. Condition:更复杂的条件判断(比如“金额大于10000”)
  3. Effect:Permit或Deny

来看一个XACML的简化例子:

<Policy PolicyId="view-doc-policy" RuleCombiningAlg="deny-overrides">
  <Target>
    <AnyOf>
      <AllOf>
        <Match MatchId="string-equal">
          <AttributeValue DataType="string">view</AttributeValue>
          <AttributeDesignator Category="action:action-id"/>
        </Match>
      </AllOf>
    </AnyOf>
  </Target>
  <Rule RuleId="deny-confidential" Effect="Deny">
    <Target>
      <AnyOf>
        <AllOf>
          <Match MatchId="string-equal">
            <AttributeValue DataType="string">confidential</AttributeValue>
            <AttributeDesignator Category="resource:classification"/>
          </Match>
        </AllOf>
      </AnyOf>
    </Target>
  </Rule>
  <Rule RuleId="permit-others" Effect="Permit"/>
</Policy>

我曾经踩过的坑:XACML的XML命名空间和AttributeDesignator的Category写错,导致策略永远匹配不上。调试了整整两天才发现是resource-id的大小写问题。嗯,XML的严格性有时候真是双刃剑。

ALFA语言:写给人类读的策略语言

XACML的XML太啰嗦了,对吧?Axiomatics公司搞了个ALFA(Axiomatics Language for Authorization),说白了就是XACML的“语法糖”。

我个人习惯用ALFA来写原型,因为它更像自然语言:

namespace com.example.document {
  policy viewDocument {
    target clause action.actionId == "view"
    apply denyOverrides

    rule denyConfidential {
      target clause resource.classification == "confidential"
      deny
    }

    rule permitOthers {
      permit
    }
  }
}

你看,同样的逻辑,ALFA比XACML简洁了不止一个量级。而且它可以直接编译成XACML,兼容性没问题。

我的建议:如果你团队里有人不熟悉XML,先用ALFA写策略原型,再转成XACML部署。我在两个项目里都这么干,效率提升很明显。

JSON策略表示法:云原生的选择

现在很多云原生ABAC系统(比如AWS IAM、Google Cloud IAM)都用JSON来表示策略。JSON的好处是轻量、易解析、前端友好。

一个典型的JSON策略长这样:

{
  "policyId": "view-doc-policy",
  "ruleCombining": "deny-overrides",
  "rules": [
    {
      "ruleId": "deny-confidential",
      "effect": "Deny",
      "condition": {
        "resource.classification": "confidential"
      }
    },
    {
      "ruleId": "permit-others",
      "effect": "Permit"
    }
  ]
}

JSON表示法没有XACML那么严格的标准,但胜在灵活。我在做微服务网关的ABAC时,就用的JSON策略,因为可以直接存在Redis里,解析速度也快。

特性 XACML ALFA JSON
标准化程度 OASIS标准 厂商扩展 无统一标准
可读性 差(XML冗长) 好(类自然语言) 中等
解析性能 慢 需编译 快
适用场景 企业级、跨系统 策略设计阶段 云原生、微服务

策略组合算法:当规则打架时

你想想看,一个请求可能匹配多条规则。一条说Permit,另一条说Deny,到底听谁的?这就是策略组合算法要解决的问题。

XACML定义了四种标准算法:

  • Deny-overrides:只要有一条规则是Deny,结果就是Deny。安全优先。
  • Permit-overrides:只要有一条规则是Permit,结果就是Permit。开放优先。
  • First-applicable:按顺序匹配,第一条生效。性能最好。
  • Only-one-applicable:只能有一条规则匹配,否则报错。最严格。

核心原则:Deny-overrides是最常用的安全策略。我在金融项目里一律用Deny-overrides,宁可误杀,不可放过。Permit-overrides适合内部协作系统,比如知识库。

来看一个Deny-overrides的决策流程:

请求: 用户A 试图 view 文档D (classification=confidential)

规则1: denyConfidential → 匹配 → Deny
规则2: permitOthers → 匹配 → Permit

组合算法: Deny-overrides
结果: Deny (因为Deny优先)

为什么会这样?因为Deny-overrides的逻辑是“只要有任何一条规则说Deny,整个结果就是Deny”。哪怕后面有100条Permit规则,也改变不了。

我曾经犯过的错:在Permit-overrides策略里,不小心加了一条过于宽泛的Permit规则,结果把机密文档都开放了。后来我养成了习惯:写策略时先画决策树,再编码。

策略组合算法的选择指南

我根据项目经验,整理了一个简单的选择表:

场景 推荐算法 原因
金融交易、医疗数据 Deny-overrides 安全第一,宁可拒绝
内部知识库、协作平台 Permit-overrides 开放协作,减少阻碍
高性能网关、CDN First-applicable 性能优先,减少计算
合规审计、严格管控 Only-one-applicable 避免规则冲突

本章知识体系

下面这张图,是我梳理的本章核心脉络:

策略语言与语法知识体系 策略语言与语法知识体系 XACML 标准 ALFA 语言 JSON 表示法 核心要素:Target / Condition / Effect 策略组合算法 Deny-overrides 安全优先 Permit-overrides 开放优先 First-applicable 顺序优先 Only-one-applicable 严格唯一 选择指南:场景 → 推荐算法 → 原因 金融用Deny-overrides,内部协作用Permit-overrides,高性能用First-applicable 三种语言 + 四种算法 = 完整的ABAC策略表达体系

嗯,这一章的内容就到这里。策略语言是ABAC的“血肉”,没有它,再好的模型也只是空架子。你写策略时,多想想“如果规则冲突了怎么办”,这比纠结语法细节更重要。