RBAC 权限系统设计:从理论模型到架构落地
角色、权限、资源——一套干净的权限体系该长什么样?
大部分系统的权限设计不是"做不了",而是"做乱了"。本文帮你建立一套完整的 RBAC 思维框架,从理论模型到架构决策,把权限这件事想清楚。
🎯 为什么需要 RBAC?
先看三个你一定见过的场景:
场景一:硬编码地狱
if user.is_admin:
allow()
elif user.role == "editor":
allow()
elif user.id == resource.owner_id:
allow()
else:
deny()
这种代码散落在几十个接口里。产品经理说"加一个'内容审核员'角色",你得翻遍整个代码库,找到所有 if user.role == ... 的地方逐个修改。改漏一个就是线上事故。
场景二:ACL 爆炸
直接给每个用户分配权限——用户 A 能访问资源 1、2、3,用户 B 能访问资源 2、4、5。10 个用户还行,1000 个用户时,你的权限表有几万条记录,新来一个人要手动配几十条权限,离职一个人要逐条回收。
场景三:权限和业务耦合
权限检查的逻辑写在业务代码里,改个权限策略要改十个微服务,发十次版本。服务 A 检查了权限,服务 B 忘了检查,同一个用户在不同入口看到的东西不一样。
这三个场景的根本问题都一样:缺少一个中间抽象层。
RBAC(Role-Based Access Control)就是这个抽象层——在用户和权限之间插入"角色",让权限管理从 O(用户数 × 权限数) 降到 O(角色数)。
主流权限模型对比
| 模型 | 核心思路 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| ACL | 用户 → 资源,直接绑定 | 简单直观 | 用户多了管不过来 | 文件系统、小团队 |
| RBAC | 用户 → 角色 → 权限 | 管理成本低,模型清晰 | 不擅长动态条件判断 | 企业应用、SaaS、API 平台 |
| ABAC | 基于属性(时间、地点、部门等)动态判断 | 极其灵活 | 策略复杂,调试困难 | 零信任架构、合规要求极高的场景 |
大多数系统,RBAC 就够了。ABAC 是"你确定需要的时候再上"的方案。
🧠 RBAC 的核心概念
RBAC 的模型其实很简洁,就四个实体加三层关系:
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐
│ │ 多对多│ │ 多对多│ │ │ │
│ User │──────│ Role │──────│ Permission │─────→│ Resource │
│ (用户) │ │ (角色) │ │ (权限) │ │ (资源) │
└──────────┘ └──────────┘ └──────────────┘ └──────────┘
│
┌─────┴─────┐
│ Session │
│ (会话) │
└───────────┘
四个核心实体:
| 实体 | 含义 | 示例 |
|---|---|---|
| User | 系统中的操作者 | 张三、李四、某个 API Key |
| Role | 一组权限的集合,代表一种职能 | 管理员、编辑、AI 用户 |
| Permission | 对某资源执行某操作的许可 | user:create、article:delete |
| Resource | 被保护的对象 | 用户数据、文章、AI 生成能力 |
"角色"为什么是关键?
没有角色的世界:
张三 → 能创建用户
张三 → 能删除用户
张三 → 能查看报表
张三 → 能导出数据
李四 → 能创建用户
李四 → 能删除用户
李四 → 能查看报表
李四 → 能导出数据
...(每个人重复一遍)
有角色的世界:
"管理员" → 能创建用户、能删除用户、能查看报表、能导出数据
张三 → 管理员
李四 → 管理员
新来一个管理员?分配一个角色就行。权限要调整?改一次角色定义,所有人生效。
这就是间接层的力量——计算机科学里那句老话:"任何问题都可以通过增加一层间接来解决。"
🏛️ NIST RBAC 模型:四个层级
美国国家标准与技术研究院(NIST)定义了 RBAC 的标准模型,分为四个层级。你不需要每个项目都实现到最高级,但理解每一级解决什么问题,能帮你做出正确的设计决策。
RBAC0:基础模型
最简单的形态——用户、角色、权限,三层关系,没有花活。
用户 ──( 多对多 )──→ 角色 ──( 多对多 )──→ 权限
解决的问题:把权限从用户身上剥离,统一通过角色管理。
典型场景:一个内部管理后台,就三种人——管理员、运营、访客,权限固定不变。
大多数中小系统停在这一级就够了。
RBAC1:角色继承
在 RBAC0 基础上,角色可以继承其他角色的权限。
┌────────────┐
│ 超级管理员 │ ← 继承"管理员"的所有权限 + 额外权限
└─────┬──────┘
│ 继承
┌─────┴──────┐
│ 管理员 │ ← 继承"编辑"的所有权限 + 用户管理
└─────┬──────┘
│ 继承
┌─────┴──────┐
│ 编辑 │ ← 基础内容权限
└────────────┘
解决的问题:消除权限重复定义。没有继承的话,"管理员"要手动包含"编辑"的全部权限,改了"编辑"的权限还得同步改"管理员"。
两种继承模式:
| 模式 | 含义 | 约束 |
|---|---|---|
| 一般继承 | 角色之间形成偏序关系(有向无环图) | 一个角色可以继承多个父角色 |
| 受限继承 | 角色之间形成严格树状结构 | 一个角色只能有一个直接父角色 |
典型场景:企业组织架构——部门经理继承普通员工的权限,总监继承经理的权限,CTO 继承总监的权限。
RBAC2:约束
在 RBAC0 基础上,给角色分配加上规则约束。
三种核心约束:
| 约束 | 含义 | 业务场景 |
|---|---|---|
| 互斥角色 | 同一用户不能同时拥有两个冲突角色 | 出纳和审计不能是同一个人(职责分离) |
| 角色容量 | 一个角色最多分配给 N 个用户 | 系统最多 3 个超级管理员 |
| 先决条件 | 拥有角色 A 的前提是先拥有角色 B | 要当"高级编辑",必须先是"编辑" |
解决的问题:防止权限滥用和利益冲突。
典型场景:金融系统——申请贷款的人不能同时是审批贷款的人;ERP 系统——创建采购单和审批采购单必须是不同的人。
RBAC3:统一模型
RBAC1 + RBAC2 的完整组合——既有角色继承,又有约束规则。
┌──────────────────────────────────────────┐
│ RBAC3 = RBAC1 + RBAC2 │
│ │
│ ┌────────────┐ ┌────────────┐ │
│ │ RBAC1 │ │ RBAC2 │ │
│ │ 角色继承 │ │ 约束规则 │ │
│ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ RBAC0 │ │
│ │ 基础模型 │ │
│ └─────────────┘ │
│ │
└──────────────────────────────────────────┘
需要 RBAC3 的场景:大型企业、政府机关、金融合规——既要分层授权(继承),又要防止权力集中(互斥约束)。
你该选哪一级?
| 系统规模 | 推荐层级 | 理由 |
|---|---|---|
| 小型项目 / MVP | RBAC0 | 简单就是美,别过度设计 |
| 中型 SaaS / 企业应用 | RBAC0 或 RBAC1 | 角色继承能省不少维护成本 |
| 大型企业 / 合规敏感行业 | RBAC2 或 RBAC3 | 互斥约束是审计和合规的硬要求 |
⚙️ 权限的命名与组织
权限设计得好不好,很大程度取决于命名规范。一套好的命名能让你光看权限名就知道它控制的是什么。
命名模式:resource:action
推荐使用冒号分隔的层级命名:
resource:action
resource:sub-resource:action
示例:
| 权限名 | 含义 |
|---|---|
user:create | 创建用户 |
user:delete | 删除用户 |
article:publish | 发布文章 |
ai:image:generate | AI 图片生成 |
ai:voice:generate | AI 语音合成 |
report:financial:export | 导出财务报表 |
通配符支持:
为了减少权限条目,可以支持通配符匹配:
| 权限 | 匹配 |
|---|---|
ai:* | ai:image:generate、ai:voice:generate、ai:text:generate... |
user:* | user:create、user:read、user:update、user:delete |
* | 一切权限(超级管理员专用) |
通配符匹配的好处是,给 admin 角色分配一个 * 就完事了,不用每加一个新权限就去更新 admin 的权限列表。
粒度选择
权限可以做到多细?取决于你愿意付出多少复杂度。
| 粒度 | 控制的是什么 | 示例 | 复杂度 | 在哪实现 |
|---|---|---|---|---|
| 路由级 | 能不能调这个 API | 有 ai:image:generate 才能调 /api/ai/images/generate | 低 | 网关层 |
| 操作级 | 能不能做这个动作 | 有 article:publish 才能发布,article:draft 只能存草稿 | 中 | 服务层 |
| 数据级 | 能看到哪些数据 | 普通用户只能看自己的订单,客服能看所有人的 | 高 | 业务代码 |
| 字段级 | 能看到哪些字段 | 普通用户看不到身份证号,管理员能看 | 很高 | 业务代码 |
实际建议:路由级和操作级用 RBAC 管,数据级和字段级交给业务逻辑。不要试图用 RBAC 解决所有权限问题——它不是银弹。
🔍 RBAC 放在哪一层?
这是做微服务架构时最关键的决策点。权限检查放的位置不同,整个系统的设计哲学就完全不同。
方案一:网关层集中检查
客户端 → API Gateway → [权限检查] → 后端服务
│
↓
认证服务
(查角色、查权限)
工作方式:每个请求到达网关时,网关调用认证服务验证 Token 并检查权限。通过了才转发给后端,后端服务完全信任网关传过来的用户信息。
优点:
- 后端服务零认证代码,只管业务逻辑
- 权限策略集中管理,改一处全局生效
- 新增权限控制只需改网关配置,不用改业务服务
- 密钥只在认证服务里,不用分发给每个微服务
缺点:
- 只能做到路由级权限,"用户 A 只能看自己的数据"这种网关做不了
- 认证服务是单点故障,挂了全系统挂
- 每个请求都多一次网关到认证服务的内部调用
适合:API 平台、AI 服务、对外开放的接口——权限模型简单,主要是"能不能用"的问题。
方案二:服务层各自检查
客户端 → API Gateway → 后端服务 A → [自己验 Token,自己查权限]
→ 后端服务 B → [自己验 Token,自己查权限]
→ 后端服务 C → [自己验 Token,自己查权限]
工作方式:网关只做路由转发(或简单的 Token 存在性检查),每个后端服务自己验证 Token、查询权限、做访问控制。
优点:
- 可以做任意粒度的权限控制(数据级、字段级)
- 没有认证单点故障(每个服务独立验证)
- 服务间零信任,安全性更高
缺点:
- 每个服务都要写认证中间件,重复实现
- JWT 密钥要分发给每个服务,密钥管理复杂
- 权限逻辑散落在各个服务里,改起来要协调多个团队
- 新服务接入成本高——要把认证这套东西抄一遍
适合:大型团队、服务间有复杂的数据权限需求、合规要求每个服务必须独立鉴权。
方案三:混合模式(推荐)
客户端 → API Gateway → [粗粒度权限检查] → 后端服务 → [细粒度业务规则]
│
↓
认证服务
(验 Token、查角色)
工作方式:
- 网关层做粗粒度检查——验证 Token、检查"这个人能不能调这个 API"
- 服务层做细粒度控制——"这个人能看哪些数据"、"这个字段要不要脱敏"
分工表:
| 层级 | 负责什么 | 示例 |
|---|---|---|
| 网关 | 身份认证 + 路由级权限 | Token 合法吗?有 ai:image:generate 权限吗? |
| 业务服务 | 数据级/字段级权限 | 这个用户只能查自己的订单;手机号对普通用户脱敏 |
这是实际生产中最常见的模式——把 RBAC 做的事和业务规则做的事分开。RBAC 管"能不能进门",业务规则管"进门后能看什么"。
三种方案对比
| 维度 | 网关层 | 服务层 | 混合模式 |
|---|---|---|---|
| 后端改动量 | 零 | 大 | 小 |
| 权限粒度 | 路由级 | 任意级 | 路由级 + 数据级 |
| 单点风险 | 认证服务 | 无 | 认证服务(可缓解) |
| 密钥管理 | 集中 | 分散 | 集中 |
| 新服务接入 | 加配置 | 写代码 | 加配置 + 少量代码 |
| 适合团队 | 小/中团队 | 大团队 | 通用 |
⚠️ 设计中的常见陷阱
1. "上帝角色"反模式
一个 admin 角色拥有系统所有权限,所有需要"高权限"的人都被扔进 admin。
问题:运维人员需要重启服务的权限,被塞进 admin 后,他也能删除用户数据了。
正确做法:按职能拆分——ops_admin(运维管理员)、user_admin(用户管理员)、content_admin(内容管理员),各管各的。
2. 权限爆炸
每个按钮、每个字段都建一个权限——button:save:click、field:email:visible...
问题:权限表膨胀到几百条,没人记得住,分配角色时要勾选几十个复选框。
正确做法:权限的粒度停在操作级(article:create、article:publish),UI 层面的可见性交给前端根据角色自行判断。
3. 角色即权限
直接在业务代码里判断角色名:
if user.role == "editor":
allow_edit()
问题:角色和行为硬耦合了。如果以后 reviewer 角色也需要编辑权限呢?又回到了到处改代码的老路。
正确做法:永远检查权限而不是角色。角色是权限的容器,业务代码不应该关心用户是什么角色,只关心用户有没有某个权限。
4. 忘记时效性
角色分配出去就是永久的,没有过期机制。
问题:临时工离职了,他的管理员权限还在;外包人员项目结束了,他还能访问内部系统。
正确做法:角色分配支持 expires_at 过期时间。定期审查不活跃的角色分配。
5. 忽略审计
不记录"谁在什么时候给谁授了什么权限"。
问题:出了安全事故,查不到权限是怎么泄露的。合规审计时拿不出证据。
正确做法:每次角色的分配、撤销、变更都记录审计日志——操作人、目标用户、角色、时间、IP 地址。这不是可选功能,是必备功能。
📝 总结
RBAC 设计检查清单
| 决策点 | 问自己 | 推荐选择 |
|---|---|---|
| 模型层级 | 需要角色继承吗?需要互斥约束吗? | 中小系统 RBAC0 就够,有组织架构需求上 RBAC1 |
| 权限粒度 | 需要控制到数据级/字段级吗? | 路由级用 RBAC,数据级交给业务逻辑 |
| 落地位置 | 权限检查放在网关还是服务里? | 混合模式:网关管粗粒度,服务管细粒度 |
| 权限命名 | 有没有统一的命名规范? | resource:action 模式,支持通配符 |
| 角色设计 | 角色是按职能分的吗?有没有"上帝角色"? | 按职能拆分,admin 也要分级 |
| 时效管理 | 角色有过期机制吗? | 支持 expires_at,定期审查 |
| 审计日志 | 权限变更有记录吗? | 必须有,这是合规的底线 |
| 代码规范 | 业务代码里检查的是角色名还是权限名? | 永远检查权限,不要检查角色 |
RBAC 不复杂,但做对它需要在设计阶段就想清楚几个关键问题。别等到权限逻辑散落在几十个服务里、几百个 if role == ... 之后再来重构——那时候的成本是设计阶段的十倍。
先把模型定对,再把位置选好,最后把命名理清。 剩下的就是工程实现的事了。