很少有技术论文像 Zanzibar: Google’s Consistent, Global Authorization System(Pang et al., USENIX ATC 2019)这样,在开源社区产生如此直接和广泛的影响——Auth0 OpenFGA、SpiceDB(AuthZed)、Permify、Ory Keto 等新一代授权系统全部以 Zanzibar 为蓝本。
但论文的 10 页正文留给实现者很多空白。本文聚焦于 Zanzibar 的核心设计决策和工程实现中的关键取舍。
一、关系元组(Relation Tuple):Zanzibar 的原子数据
Zanzibar 的数据模型只有一个核心概念:关系元组。它的语法是:
⟨namespace:object_id #relation user⟩
例如:
⟨doc:readme.txt #viewer user:zhangsan⟩ — zhangsan 是 readme.txt 的 viewer
⟨doc:readme.txt #owner group:eng-leads⟩ — eng-leads 组的成员是 readme.txt 的 owner
⟨group:eng-leads #member user:lisi⟩ — lisi 是 eng-leads 组的 member
Zanzibar 的查询就是:给定这些关系元组和 namespace
的配置,判断 ⟨user, relation, object⟩
是否成立。
这个模型的简洁性是它的核心力量——不需要维护 RBAC 的角色表,不需要维护 ABAC 的属性表达式。所有权限都表示为图上的三元组。
二、Namespace Config:图上的路径规则
光有元组不够。user:zhangsan 是
doc:readme.txt 的
viewer,但系统怎么知道”owner 也是 viewer”、“在
eng-leads 组的成员也是文档的 owner”?这就是 Namespace
Config(命名空间配置)的作用。
name: doc
relations:
viewer:
union:
this: {} # 直接 viewer
computed_userset:
relation: editor # editor 也是 viewer
tuple_to_userset: # doc 的 owner 是 group → group 的 member 也是 viewer
tupleset:
relation: owner
computed_userset:
relation: member
tuple_to_userset: # doc 的 folder → folder 的 viewer 也是 doc 的 viewer
tupleset:
relation: parent_folder
computed_userset:
relation: viewer
editor:
union:
this: {}
computed_userset:
relation: owner # owner 也是 editor
owner:
this: {}
parent_folder:
this: {}Namespace Config 相当于定义了一个小型的”关系代数解释器”——它描述了每种关系的推导规则:
this:直接关系(有显式的 relation tuple)。computed_userset:在同一 namespace 内的继承关系(A 角色的成员自动获得 B 角色)。tuple_to_userset:跨 namespace 的传递关系——从当前对象的一个关系(如parent_folder),跳到目标对象的另一个关系(如viewer),形成间接访问路径。
给定元组集合和 namespace config,Zanzibar 对每个 Check 请求做图搜索——从用户节点出发,沿元组和 config 定义的边探索,直到找到到达目标对象+关系的路径,或者穷尽所有路径。
三、Consistency Model:Zookie 的真正含义
Zanzibar 论文最被引用的一句话是”支持全局一致性和低延迟”。但这里的”一致性”是一个精细的定义,不是”每次读取都是最新数据”这种强一致性。
3.1 Zookie:不透明一致性令牌
Zanzibar 在每个写操作后返回一个称为 Zookie 的不透明令牌(类似于 Spanner 的 TrueTime timestamp 的编码)。后续的 Check 请求可以附带这个 Zookie,Zanzibar 保证 Check 看到的数据不早于 Zookie 对应的时间点。这称为”Read-at-least-as-recent-as”一致性。
写: ACL change ⟨doc:x #viewer user:A⟩ → remove
返回: Zookie_123 (编码了写操作的时间戳)
读: Check ⟨user:A, viewer, doc:x⟩ with Zookie_123
保证: 返回 "denied"(必定反映 remove 操作之后的状态)
不附带 Zookie 的 Check 请求可能看到稍微过时的缓存数据。论文中提到 Zanzibar 在保持全局一致性(附带 Zookie)时 p99 延迟 < 100ms,在优先延迟时(不附带 Zookie)p99 < 10ms。
3.2 为什么要有 Zookie
不附带 Zookie 的 Check 会出现”幽灵权限”问题——用户刚刚被你移除了文档的访问权限,但 Check 仍返回 true,因为它命中了一个还没失效的缓存。反过来也会出现——用户刚被加为 viewer,但 Check 返回 false。
但实际上大多数 Check 请求不需要 Zookie。Google Photos 的用户在分享相册后,被分享者可能过了一两秒才看到——这是可接受的。需要 Zookie 的场景是那些”写后必须生效”的——如权限变更后用户重试操作,期望立即反映变更。
工程含义:如果你的应用场景容许 1-3 秒的权限变更延迟(绝大多数 SaaS 场景),你可以使用无 Zookie 的 Check,享受更好的性能和更低的延迟。Zookie 的一致性成本对大多数业务是不必要的。
四、开源的 Zanzibar 实现对比
| 系统 | 语言 | 存储后端 | 一致性模型 | 成熟度 |
|---|---|---|---|---|
| SpiceDB | Go | CockroachDB / PostgreSQL / MySQL / Spanner | Zookie 等效(ZedToken) | CNCF Sandbox,AuthZed 商业支持 |
| OpenFGA | Go | PostgreSQL / MySQL | 无 Zookie(最终一致性) | CNCF Sandbox,Auth0/Okta 支持 |
| Permify | Go | PostgreSQL | 无 Zookie | 较新,Schema DSL 更简洁 |
| Ory Keto | Go | PostgreSQL / MySQL | 无 Zookie | Ory 生态的一部分 |
选型参考: - 需要严格一致性 → SpiceDB(唯一提供 Zookie 等效机制的开源实现)。 - 需要最大生态支持 → OpenFGA(Okta 旗下,DSL 与 Zanzibar 论文最接近,有官方 SDK 支持 10+ 语言)。 - 已经在用 Ory Hydra/Kratos → Ory Keto(统一生态)。 - 偏好更简洁的 DSL → Permify(Schema 语法比 Zanzibar 的 namespace config 更接近自然语言)。
五、自己实现 Zanzibar 时要面对的工程问题
5.1 图遍历的性能
Zanzibar 的核心操作是图搜索——从用户节点出发,按 namespace config 定义的规则探索路径。问题在于:路径深度可能不可控。
一个 namespace config 包含 tuple_to_userset
的链条:user → group1 → group2 → group3 → ...。无限的成员组嵌套会导致路径搜索发散。Zanzibar
论文中提到了”config-driven depth limits”——每个 namespace
可以指定最大搜索深度。在生产实现中,这个限制的默认值通常设为
50-100。
5.2 Subject Set 展开
group:eng-leads #member 是一个 subject
set(主体集合)——包含所有满足
⟨group:eng-leads #member *⟩ 的主体。当查询
user:zhangsan 是不是
doc:readme.txt 的 viewer,而
viewer 包含 group:eng-leads 的 member,Zanzibar
需要检查 user:zhangsan 是否属于
group:eng-leads 的
member。这是一个递归展开。
大的 group(几万到几十万成员)在展开时需要高效的数据结构。Zanzibar 使用”跨越边”索引技术(Leapfrog Triejoin,用于 Spanner 的查询引擎),但开源实现大多采用更简单的方案——B-Tree 索引 + 递归查询。
5.3 Wildcard 权限
Zanzibar 支持 user:*(所有用户都是
viewer)。这对公开文档很有用,但在图搜索中引入了一个全局集合——无论什么用户,都命中这个条件。实现的工程处理是短路求值:如果发现了
user:* 的匹配规则且用户无排除规则,直接返回
true。
上一篇:RBAC、ABAC、ReBAC:权限模型怎么选 下一篇:OPA、Cedar 与策略引擎落地
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【身份与访问控制工程】IAM 全景:为什么这是高价值赛道
从 2020 年 SolarWinds 到 2024 年 Okta 支持系统泄露,身份基础设施的安全失败反复证明一件事:IAM 不是 IT 支撑系统,而是安全架构的承重墙。本文建立现代 IAM 的全景地图——从认证协议、令牌体系、权限模型到身份治理与平台选型,给出 5 个贯穿全系列的核心问题。
【身份与访问控制工程】RBAC、ABAC、ReBAC:权限模型怎么选
RBAC 简单但会角色爆炸,ABAC 灵活但策略管理失控时更可怕,ReBAC 表达力强但引入了图遍历的性能约束。三种模型不是'选一个升级另一个'的线性关系,而是在表达能力、管理成本和性能三者之间做工程权衡。本文从每种模型的本质数据结构出发,拆解选型框架。
【身份与访问控制工程】OPA、Cedar 与策略引擎落地
OPA 是 CNCF 的策略引擎标准答案,Rego 是它的策略语言;Cedar 是 AWS 开源的新竞争者,基于 Rust 的 WASM 编译执行、语法更接近 SQL。两者在架构模式(sidecar vs 中心化)、策略语言设计哲学和性能特征上有根本差异。本文从策略引擎的架构模式出发,拆解 OPA Rego 的核心语义与性能限制、Cedar 的设计取舍,以及策略即代码(Policy as Code)在 CI/CD 中的落地。
【身份与访问控制工程】B2B SaaS 多租户权限设计
多租户权限系统是 IAM 中工程复杂度最高的场景之一——每个租户想要自己的角色、自己的组织树、自己的审批流和完全隔离的数据。这四种需求会互相冲突。本文从租户隔离模型出发,拆解四层权限架构、租户级 RBAC 的扩展方案、组织树与数据权限的联动,以及跨租户授权(如第三方服务商访问客户数据)的架构设计。