如何为应用添加基于角色的访问控制(RBAC)
要添加基于角色的访问控制,需要为每位用户分配一个或多个角色(例如管理员、编辑、查看者),定义每个角色可以执行哪些操作,并且在服务端对每个请求检查这些权限——而不仅仅是隐藏按钮。还要检查被访问的记录是否属于该用户的账户或团队。先在数据库中设置几个固定角色,然后通过尝试直接访问其他用户的数据来进行测试。
身份认证与授权
两个容易混淆的概念:
- 身份认证回答的是你是谁?——即登录。参见如何为应用添加登录功能。
- 授权回答的是你被允许做什么?——即已登录的用户可以访问哪些页面、记录和操作。
RBAC 是最常见的授权方式。这一点很重要:截至 2026 年 9 月,失效的访问控制在 OWASP Top 10:2025(广泛使用的最严重 Web 应用安全风险清单)中排名第一。典型的失效情形包括:用户通过修改 URL 中的 ID 读取其他用户的记录,或普通用户调用仅限管理员使用的 API 路由。
选择模型
| 模型 | 工作方式 | 适用场景 | 权衡 |
|---|---|---|---|
| 单一角色字段 | users.role = admin / member | 只有两三个角色的小型应用 | 当同一个人需要在不同场景下拥有不同角色时,会变得混乱 |
| 角色 + 权限 | 角色映射到已命名的权限(invoices.approve);代码检查权限 | 规则会不断增长的应用 | 表更多;需要维护一份权限列表 |
| 按团队(多租户)划分的角色 | 成员关系表:用户 + 组织 + 角色 | 人员属于团队或工作区的 SaaS | 每个查询都必须限定在对应的组织范围内 |
| 基于属性或关系的模型 | 基于记录属性的规则(“作者可以编辑自己的帖子”)或基于关系的规则(与…共享) | 文档、共享及复杂的工作流 | 较难推理;可考虑专门的库或服务 |
| 认证服务商的角色 | 你的登录服务商存储角色或组织成员关系 | 已在使用托管认证服务的应用 | 依赖该服务商;仍需在你的服务端强制执行 |
实际上,大多数应用会结合两件事:角色决定某人可以执行哪类操作,归属关系决定他们可以对哪些记录执行这些操作。“编辑可以编辑帖子”是角色规则;“……且限于其自己工作区中的帖子”则是归属规则。两者你都需要。
从比你想象中更简单的方案开始。管理员、成员和查看者这三个角色,就足以满足相当多应用的需求。之后拆分一个角色,比理清十个没人能解释清楚的角色要容易得多。
编写代码之前先设计角色
先用通俗的语言写一张权限表。它既可以作为给 AI 的规格说明,也可以作为你的测试计划:
| 操作 | 所有者 | 管理员 | 成员 | 查看者 |
|---|---|---|---|---|
| 查看团队中的项目 | ✓ | ✓ | ✓ | ✓ |
| 创建和编辑项目 | ✓ | ✓ | ✓ | |
| 删除项目 | ✓ | ✓ | ||
| 邀请和移除成员 | ✓ | ✓ | ||
| 更改角色 | ✓ | |||
| 账单 | ✓ |
也要决定边界情况:最后一位所有者能否离开?管理员能否移除所有者?被移除的用户会看到什么?
分步操作
1. 先确保登录功能正常
RBAC 构建在身份认证之上。服务端必须能够可靠地知道每个请求是谁发出的,依据是会话或经过验证的令牌——绝不能依据浏览器在请求体中发送的用户 ID。
2. 将角色存储在数据库中
对于只有单一团队的应用,用户表中的 role 字段就足够了。对于多团队的情况,使用成员关系表:
create table memberships (
user_id uuid references users(id),
organization_id uuid references organizations(id),
role text not null check (role in ('owner','admin','member','viewer')),
primary key (user_id, organization_id)
);
绝不要让用户通过普通的个人资料更新来设置自己的角色。角色变更应通过专门的、经过权限检查的端点进行。
3. 在服务端集中检查权限
编写一个统一的辅助函数,例如 can(user, 'projects.delete', project),并在每个读取或修改数据的 API 路由、服务端操作和后台任务中调用它。集中的一个函数比分散在代码各处的检查更容易审查和测试。默认拒绝:如果没有任何规则允许某项操作,则拒绝执行。
4. 限定每个查询的范围
每个查询团队数据的语句都应包含团队信息:使用 where organization_id = :currentOrg,而不只是 where id = :id。这样才能防止有人把 /projects/123 改成 /projects/124 就看到另一家公司的项目。对于其他团队的记录,应返回“未找到”而不是“禁止访问”,以免暴露这些记录的存在。
5. 在关键位置添加数据库级保护
Postgres 行级安全(RLS)允许数据库强制规定每位用户可以看到哪些行。如果浏览器直接与数据库通信(例如某些托管后端),这一点至关重要;在其他情况下,它也是敏感表的有用第二层防护。
6. 在界面中反映角色
现在隐藏或禁用用户无权执行的操作,使应用不会让人困惑。这只是为了易用性;真正起保护作用的是服务端的检查。
7. 记录敏感操作
记录谁在何时更改了角色、删除了数据或导出了记录。审计日志有助于处理支持请求和安全事件。
8. 像攻击者一样测试
以每个角色登录,并尝试权限表中的每一项操作。然后尝试那些本不应成功的操作:以查看者身份调用管理员 API 路由,在 URL 和请求中修改 ID,以及把自己作为最后一位所有者移除。规则确定之后,值得为权限表编写自动化测试。
行之有效的提示词
添加基于角色的访问控制。每个组织的角色:所有者、管理员、成员、查看者,存储在成员关系表中。权限定义见下表:[粘贴你的表格]。创建一个服务端的 can(user, action, resource) 辅助函数,默认拒绝,并在每个 API 路由中使用它。所有查询都按 organization_id 限定范围。在界面中隐藏用户无法执行的操作,但绝不能依赖这一点来保证安全。
审查本应用中的每一个 API 路由和服务端函数。对于每一个,列出它读取或修改了哪些数据,是否检查用户已登录,是否检查其角色,以及是否将查询限定在其所属组织内。修复所有缺少检查的地方,并向我展示该清单。
添加一个仅限管理员访问的 /admin 页面,让所有者可以更改成员的角色。禁止移除或降级最后一位所有者,记录每次角色变更的执行人和时间,并确保角色变更端点会拒绝任何非所有者的请求。
常见错误
- **只在前端检查角色。**API 仍然对任何调用它的人开放。
- 信任客户端传来的 ID——例如从请求体而不是会话中获取
userId或role。 - 未限定范围的查询,仅凭记录 ID 进行查询。
- 只在页面上检查角色,而没有在 API 路由、导出、文件下载或 Webhook 中检查。
- 在代码中硬编码通过邮箱地址识别管理员(
if email === 'me@...'),这很容易被遗忘,也很难修改。 - 过早设置过多角色,导致六个月后没人能解释清楚。
- 会话过期不及时:被降级的用户在退出登录之前仍保有管理员权限。对于敏感操作,应从数据库检查角色,或刷新角色信息。
RBAC 检查清单
- 已用通俗语言写出权限表,并已确定边界情况
- 服务端从会话中识别用户,绝不从请求数据中获取
- 角色存储在数据库中;用户无法更改自己的角色
- 一个集中的权限检查,默认拒绝,并在每条路由中使用
- 每个查询都限定在用户的组织或归属范围内
- 浏览器能够访问数据库的地方已启用行级安全
- 界面隐藏了用户无法执行的操作(仅为易用性)
- 敏感操作已记录日志
- 已测试每个角色,包括直接调用 API 和修改 ID 的情况
访问控制只是更长清单中的一项;AI 构建应用的安全检查清单涵盖了其余内容。如果你要为自己的员工构建工具,如何搭建内部工具展示了角色在其中的位置。
Mythex 中的 RBAC
Mythex 不为你应用的用户提供内置的登录或角色系统。你需要选择认证库或服务商,并描述角色,代理会将它们接入你的代码和项目的 Postgres 数据库(为应用添加登录功能)。Mythex 自身的工作区角色(例如所有者和成员)控制的是谁可以编辑你的 Mythex 项目,而不是谁可以使用你发布的应用。在构建之前,使用计划模式先确认权限表;然后使用上面的审查提示词,并在发布之前于预览中测试每个角色。文档中的安全提示词是很有用的后续参考。
常见问题
什么是基于角色的访问控制?
基于角色的访问控制(RBAC)根据每位用户所持有的角色(例如管理员、编辑或查看者)来决定其可以做什么。权限附加在角色上,而不是附加在具体的个人身上,因此改变某人的权限只需改变其角色。
身份认证和授权有什么区别?
身份认证用于确认某人是谁,通常通过登录完成。授权则决定已登录的人可以查看和执行哪些操作。RBAC 是实现授权的一种方式。
在界面中隐藏按钮就足以限制访问了吗?
不够。任何人都可以直接调用你的 API,因此隐藏按钮只会改变人们看到的内容。每一个读取或修改数据的请求,都必须在服务端根据用户的角色以及记录的归属进行检查。
我应该使用 Postgres 行级安全吗?
行级安全允许数据库本身强制规定每位用户可以读取或写入哪些行,是一道很有力的安全保障,尤其是在前端直接与数据库通信的情况下。但它会增加复杂性,因此许多应用在服务端代码中强制执行权限,并对敏感表额外使用行级安全作为补充防护层。