权限系统怎么设计?—— 从 RBAC 到 Google Zanzibar
后台系统里"谁能看哪些数据、改哪些东西"的权限,怎么设计才能既细又不乱?这篇按"从简单到复杂"四个台阶讲清楚,配一个能跑的 demo。
一、两层权限,先别混
写代码前先分清,不然会越想越乱:
- 云资源权限:谁能操作服务器、数据库、对象存储这些云资源。这用阿里云 RAM、AWS IAM 那类现成服务,运维配置,文末带一笔。
- 应用业务权限:你产品里,用户能访问哪些功能、哪些数据。这是写在自己代码里的,本文主角。
二、四个台阶:从 RBAC 到 ReBAC
权限模型不是非此即彼,而是需求越复杂,往上爬一阶。
台阶一:RBAC —— 绝大多数系统的答案
不给用户直接配权限,中间加一层"角色":用户领角色,角色带权限。
落到数据库,核心是这几张表:
users 用户
roles 角色(admin、editor、viewer…)
permissions 权限(article:read、article:edit…)
user_roles 谁是什么角色
role_permissions 角色有哪些权限
判断"这个人能不能改文章":查他的角色,看角色的权限里有没有 article:edit。
为什么不把权限直接配到用户身上?因为用户成千上万、权限又常调整,中间用角色一隔,管理量从"人数 × 权限数"降到"角色数 × 权限数"。这就是它够用于绝大多数后台的原因。
台阶二:给权限加"资源范围"
RBAC 有个隐含假设:角色是全局的——editor 对所有文章都生效。但现实里权限常常只针对某个范围:不是"能编辑文章",而是"能编辑 A 项目的文章"。
办法:授权时多带一个范围(scope)。user_roles 从 (用户, 角色) 变成 (用户, 角色, 范围):
alice editor projectA # alice 是 A 项目的 editor
alice viewer projectB # 但在 B 项目只是 viewer
判断时多一步:不光看角色有没有权限,还看这条授权的范围覆不覆盖当前要访问的资源。
当资源本身有层级(项目 → 子项目 → 文档,或公司内部常叫的"服务树"那种),范围还得支持继承:授权挂在父节点,子节点自动生效。到这一步,RBAC 已经能扛住大部分中后台系统。
台阶三:ABAC —— 规则太活,角色装不下
有些权限没法用固定角色表达,比如"只能改自己创建的、且状态是草稿的文档"。这种"看具体属性和条件"的判断,角色穷举不过来。
ABAC(基于属性)为此而生:不预定义角色,而是写规则,运行时拿请求的属性(谁、什么资源、什么状态、什么时间)去匹配:
允许 if 资源.创建者 == 当前用户 且 资源.状态 == '草稿'
云厂商的权限策略(阿里云 RAM、AWS IAM 那种 JSON policy)本质就是 ABAC——条件写在 policy 里。代价是:规则一多,排查"这个人为什么能/不能"会变难。
台阶四:ReBAC / Google Zanzibar —— 把权限变成"关系"
到了 Google Docs、网盘这类产品,权限有三个特征同时出现,前面几种都吃力:
- 对象级:每一份文档单独授权,不是全局角色;
- 可共享:用户能把文档直接分享给另一个用户;
- 可继承:文件夹的权限,自动落到里面每份文档。
Google 给自家几乎所有产品(Docs、Drive、YouTube、Calendar)做权限的系统叫 Zanzibar。它的思路是:把所有权限都表达成"关系"。
核心数据结构只有一种,叫关系元组(relation tuple),格式是 对象#关系@用户:
doc:设计稿#editor@alice → alice 是「设计稿」的 editor
doc:设计稿#viewer@bob → bob 是「设计稿」的 viewer
"分享"就是加一条元组。 你在 Google Docs 点"分享给 bob、可查看",后台干的事就是写入一条 doc:设计稿#viewer@bob。
"继承"靠让关系指向另一组关系。 先记下文档属于哪个文件夹:
doc:设计稿#parent@folder:设计部
再定一条规则:"文档的 viewer = 它自己的 viewer + 它父文件夹的成员"。于是只要把某人加进「设计部」文件夹,里面所有文档对他自动可见——继承就是这么实现的。
"检查权限"(bob 能看设计稿吗)就变成在这张关系图上找路径:要么存在直接元组 doc:设计稿#viewer@bob,要么 bob 通过某个父文件夹间接连得上。
这就是 ReBAC(基于关系的访问控制) 和 RBAC 的根本区别:
- RBAC 问的是"你是什么角色"(角色是全局的、预先定义的);
- ReBAC 问的是"你和这个具体对象之间,有没有直接或间接的关系"。
前者适合固定角色,后者适合"每个东西单独授权、还能转授和继承"。自己不用从头造 Zanzibar,有开源实现可直接用:SpiceDB(最贴合 Google 论文)、OpenFGA(Auth0 出品,上手最简单)。
四个台阶的适用线:固定角色 → RBAC;按项目/团队分 → RBAC + 范围;规则带条件 → ABAC;像 Google Docs 那样对象级共享 + 继承 → ReBAC。多数业务停在前两阶,真到协作/共享密集的产品,才需要往后走。
三、动手:最小权限系统(demo)
👉 完整代码 GitHub · rbac-demo
实现的是台阶一 + 台阶二:RBAC + 资源范围 + 一个权限校验中间件。每个请求进来,中间件查"这个用户在这个项目里的角色,有没有要求的权限",再决定放行还是 403。跑一遍能直观看到:同一个 alice,在 A 项目能改、在 B 项目改不了——这就是"资源范围"在起作用。
四、顺带一眼:云厂商怎么做
应用里的权限要自己写;但云资源的权限,云厂商做成了现成服务,模型还是上面这套:
| 身份 | 权限 | 层级 / 范围 | |
|---|---|---|---|
| 阿里云 RAM | 用户 / 组 / 角色 | 权限策略(JSON,本质 ABAC) | 资源组划范围 |
| AWS IAM | 用户 / 组 / 角色 | policy | Organizations 账号层级 |
| GCP IAM | 成员 | 角色绑定 | 资源层级(组织→文件夹→项目)沿层级继承 |
小公司接阿里云的常规姿势:不用主账号日常操作、给每人开一个 RAM 子用户、按职能建组授权、坚持最小权限、生产和测试用资源组隔开。再往下的实操是运维的活。
五、几个容易翻车的点
- 权限校验必须在服务端。前端把按钮藏起来只是 UI,接口照样能被直接调用——真正的拦截只能放服务端。
- 防越权。最常见的漏洞是"换个 id 就能看到别人的数据"(水平越权)、"普通用户调到了管理员接口"(垂直越权)。每个接口都要校验"这个人对这条具体资源有没有权限",而不只是"登录了没"。
- 从够用的那一阶起步。角色清晰就 RBAC,真有共享/继承需求再上 ReBAC;一上来套最复杂的,维护成本会反噬。
- 盯住角色数量。如果角色越建越多(每个特殊情况都新建一个),往往是缺了"资源范围"这一层——该用"角色 + 范围",而不是堆角色。
附:名词速查
- RBAC:基于角色(用户 → 角色 → 权限)。
- ABAC:基于属性 / 条件的规则判断。
- ReBAC:基于关系(实体之间连成关系图,查路径)。
- 关系元组 (relation tuple):Zanzibar 的核心数据,
对象#关系@用户。 - Zanzibar:Google 的全球授权系统,ReBAC 的代表;开源实现 SpiceDB、OpenFGA。
- IAM / RAM:云厂商管"谁能操作哪些云资源"的服务。
系列:上一篇 《鉴权怎么落地?—— 场景选型与避坑》 讲场景选型;更早 《JWT、OAuth、Token 刷新 —— 鉴权入门》 讲机制。完整代码与系列都在 GitHub:Aimee1608/backend-notes。
评论(0)
登录后参与评论。
还没有评论,来抢沙发吧。

