3. 策略语言入门:JSON格式的ABAC策略定义

好,我们正式开始写策略了。

前面两章聊了ABAC是什么、能解决什么问题。现在该动手了。我个人习惯,学任何规则引擎,先看一条最简单的策略长什么样。就像学编程,第一个程序永远是“Hello World”。

这一章,我们就写一条ABAC的“Hello World”——“允许管理员查看”。

3.1 为什么选JSON?

你可能会问:为什么不用YAML?不用XML?

嗯,这里有几个原因。第一,JSON是Web生态的事实标准。你写后端接口、存配置、做数据传输,JSON无处不在。第二,JSON的解析器几乎每种语言都有,而且性能稳定。第三,JSON的结构和ABAC的“主体-资源-动作-环境”四元组天然匹配——都是键值对嵌套。

我在项目中见过用XML写策略的团队,后来全改成JSON了。为什么?因为XML写起来太啰嗦,一个简单的策略要写十几行标签。JSON三五行搞定。

我的建议: 除非你的团队有强约束(比如必须用YAML做配置管理),否则策略语言首选JSON。简单、直接、好调试。

3.2 策略的骨架:四要素

一条ABAC策略,说白了就是回答一个问题:谁在什么条件下能对什么资源做什么操作?

拆开来看,四个要素:

要素 英文 含义 例子
主体 Subject 谁发起的请求 用户、角色、服务账号
资源 Resource 要访问的对象 API、文件、数据库记录
动作 Action 要做什么 查看、编辑、删除
环境 Environment 上下文条件 时间、IP、设备类型

我们的“允许管理员查看”策略,只用了前三个要素。环境条件暂时留空——后面章节会补上。

3.3 写一条策略:从零开始

好,直接上代码。这是我最常用的一种策略结构:

{
  "policyId": "policy-001",
  "description": "允许管理员查看任何资源",
  "effect": "Allow",
  "subjects": ["role:admin"],
  "resources": ["*"],
  "actions": ["read"],
  "conditions": {}
}

来,一行一行拆解。

  • policyId:策略的唯一标识。我习惯用“policy-”加数字,生产环境可以用UUID。
  • description:给人看的。写清楚这条策略是干什么的。别偷懒,三个月后你自己都会忘。
  • effect:只有两个值——Allow 或 Deny。这里是允许。
  • subjects:数组。这里写的是 role:admin,表示“所有拥有admin角色的主体”。
  • resources:数组。* 是通配符,表示所有资源。
  • actions:数组。read 就是查看操作。
  • conditions:对象。暂时为空,后面章节会填上时间、IP等条件。

注意: subjects、resources、actions 都是数组。为什么?因为一条策略可以匹配多个主体、多个资源、多个动作。比如“允许管理员和编辑者查看和导出文档”。数组让表达更灵活。

3.4 通配符与匹配规则

刚才用了 * 表示所有资源。通配符是策略语言里非常实用的设计。

我常用的通配符规则:

通配符 含义 例子 匹配结果
* 匹配任意 resources: ["*"] 匹配所有资源
prefix:* 前缀匹配 resources: ["doc:*"] 匹配所有文档类资源
? 单字符匹配 resources: ["doc-?"] 匹配 doc-1, doc-a 等

你想想看,如果没有通配符,你要为每个资源写一条策略,那策略数量会爆炸。通配符让策略变得简洁、可维护。

我曾经踩过一个坑: 通配符写得太宽,导致权限失控。比如 subjects: ["*"] 表示所有用户,如果配合 actions: ["delete"],那后果很严重。所以,通配符要用在合适的地方,别图省事一把梭。

3.5 策略的完整生命周期

一条策略从定义到生效,大概走这么几步:

  1. 定义:用JSON写好策略,存到文件或数据库。
  2. 加载:规则引擎启动时,把策略加载到内存。
  3. 解析:引擎把JSON解析成内部数据结构。
  4. 评估:收到请求时,引擎匹配策略,返回 Allow 或 Deny。
  5. 审计:记录评估结果,方便排查问题。

这一章我们只关注第一步——定义。后面的步骤,后面章节会逐一展开。

3.6 策略的视觉化:一张图看懂

说了这么多,不如一张图来得直观。下面是我画的“允许管理员查看”策略的评估流程:

策略评估流程:“允许管理员查看” 访问请求 加载策略 policy-001 匹配 subjects / resources / actions 主体=role:admin, 资源=*, 动作=read 返回 Allow 说明 请求携带主体角色 引擎自动匹配策略 匹配逻辑 subjects 包含 role:admin resources 匹配 * 结果 全部匹配 → Allow 任一不匹配 → Deny

这张图展示了策略评估的核心逻辑。请求进来,引擎加载策略,然后逐项匹配主体、资源、动作。全部命中,返回Allow。任何一个不匹配,就返回Deny。

3.7 实战:写一条你自己的策略

好,光看不练假把式。我给你一个场景,你试着写一条策略。

场景: 允许编辑者(editor)修改(update)文档(doc:*)。

先别往下看,自己写写看。

写完了?来,对照一下我的版本:

{
  "policyId": "policy-002",
  "description": "允许编辑者修改文档",
  "effect": "Allow",
  "subjects": ["role:editor"],
  "resources": ["doc:*"],
  "actions": ["update"],
  "conditions": {}
}

是不是差不多?嗯,这里有个细节:resources 用了 doc:*,表示所有文档类资源。如果你写成 *,那编辑者就能修改所有资源,包括系统配置——这就有安全风险了。

我的习惯: 资源路径尽量精确。能用 doc:* 就别用 *。权限控制,越精确越安全。

3.8 避坑指南

最后,分享几个我踩过的坑:

  • 策略ID重复: 两条策略用了同一个policyId,结果后加载的覆盖了前面的。排查了半天。所以,policyId一定要唯一。
  • 大小写敏感: 策略里写 role:Admin,但请求里传的是 role:admin,匹配失败。我建议统一小写,或者在引擎层做大小写归一化。
  • 空数组的歧义: subjects: [] 表示什么?表示没有主体能匹配。但有些人以为是“匹配所有”。这个一定要在文档里明确说明。
  • 通配符滥用: 前面说过了,* 用多了,权限就失控了。能用精确匹配,就别用通配符。