3. 策略语言入门:JSON格式的ABAC策略定义
好,我们正式开始写策略了。
前面两章聊了ABAC是什么、能解决什么问题。现在该动手了。我个人习惯,学任何规则引擎,先看一条最简单的策略长什么样。就像学编程,第一个程序永远是“Hello World”。
这一章,我们就写一条ABAC的“Hello World”——“允许管理员查看”。
3.1 为什么选JSON?
你可能会问:为什么不用YAML?不用XML?
嗯,这里有几个原因。第一,JSON是Web生态的事实标准。你写后端接口、存配置、做数据传输,JSON无处不在。第二,JSON的解析器几乎每种语言都有,而且性能稳定。第三,JSON的结构和ABAC的“主体-资源-动作-环境”四元组天然匹配——都是键值对嵌套。
我在项目中见过用XML写策略的团队,后来全改成JSON了。为什么?因为XML写起来太啰嗦,一个简单的策略要写十几行标签。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 策略的完整生命周期
一条策略从定义到生效,大概走这么几步:
- 定义:用JSON写好策略,存到文件或数据库。
- 加载:规则引擎启动时,把策略加载到内存。
- 解析:引擎把JSON解析成内部数据结构。
- 评估:收到请求时,引擎匹配策略,返回 Allow 或 Deny。
- 审计:记录评估结果,方便排查问题。
这一章我们只关注第一步——定义。后面的步骤,后面章节会逐一展开。
3.6 策略的视觉化:一张图看懂
说了这么多,不如一张图来得直观。下面是我画的“允许管理员查看”策略的评估流程:
这张图展示了策略评估的核心逻辑。请求进来,引擎加载策略,然后逐项匹配主体、资源、动作。全部命中,返回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: []表示什么?表示没有主体能匹配。但有些人以为是“匹配所有”。这个一定要在文档里明确说明。 - 通配符滥用: 前面说过了,
*用多了,权限就失控了。能用精确匹配,就别用通配符。