多租户 SaaS 产品权限系统设计
多租户 SaaS 产品需要提供灵活强大的权限系统,才能满足产品功能控制和租户自定义角色的需求。本文通过分层抽象用户、产品人员和开发人员各方对权限系统的不同角度的理解来分离关注点、解耦实现,既方便了各方理解,又能保证权限系统的稳定性。

BasicAI 是全球首个开源多模态训练数据平台,通过提供 AI 赋能的软件工具、数万项目提炼的本体中心和丰富的数据治理特性,来加速多模态训练数据的处理效率,进而提高 AI 工程师的建模效率。
多租户 SaaS 产品一般都拥有比较复杂的权限控制系统,并且允许租户自定义角色,本文以 BasicAI 为例介绍了多租户 SaaS 产品的权限系统设计。
术语定义
权限系统中有些术语翻译成中文后含义比较类似,容易搞混,这里先统一说明一下,特别要注意 Permission 和 Privilege 的区别。
- Role 角色,对应现实中的身份,比如经理、主管和员工
- Function 功能,从产品角度理解的系统功能,可能与具体实现的页面结构不是完全对应
- Permission 权限,包含动作和客体,比如“创建任务”,从开发角度理解的系统功能,通常按照前端页面结构来划分,如果页面粒度太粗,还可再进一步细分到页面内的操作
- Resource 资源,程序中的对象,比如菜单、按钮、链接、API 等
- Privilege 权利,包含主体、动作和客体,比如“小王可以创建任务”
架构设计

系统的权限划分粒度可粗可细,对用户来说只关心自己是什么角色,对产品人员来说希望控制某个用户可以使用哪些功能,而对开发人员来说则要在代码里校验某个用户是否能执行某个资源操作。为了便于用户、产品人员和开发人员都能从各自的角度去理解权限,从上到下,由粗到细,整个权限系统可划分为 Role、Function、Permission 和 Resource。层与层之间均为多对多关系,每层都按自己的粒度划分权限,如有变动只需调整映射关系,无需修改权限验证代码。权限管理和配置比较复杂,特别是各层之间的映射关系,最好有配套的管理后台。
如果不追求灵活性,为了简单,可以移除 Resource 层,直接在代码里每个资源操作(点击按钮、调用 API)的地方判断当前用户是否具备相应的 Permission。注意,一个资源操作可能会关联到多个 Permissions,用户只需要具备其中之一即可。但如果希望集中校验所有资源操作(比如后端在网关里统一验证 API 权限),那么就需要保留 Resource 层,验证时首先根据 Permission 和 Resource 的映射关系动态获取哪些 Permissions 能够操作当前资源,然后判断当前用户是否拥有这些 Permissions 之一。
库表设计
UML 模型

- 每个 Team 可以自定义 Role,没有关联到任何 Team 的 Role 为默认 Role,所有团队共用;
- Function 和 Permission 表的结构完全一样,只是划分维度不一样,如果产品和开发能达成一致,那么可以去掉 Function 表,Role 直接关联到 Permission,以简化实现;
- Permission 按照前端页面结构来划分,如果有导航菜单,直接按导航菜单划分就可以,需要的话也可以细到页面内的操作。为了更好控制导航菜单的隐藏或现实,也可为导航菜单定义专门的 Permissions;
- 这里假设前端没有集中校验权限的需求,只有后端 API 需要在网关里集中校验所有 API 请求,因此 Resource 表退化为了 Api 表。前端如果要集中校验权限,也可以把 Permission 跟 Resource 的映射关系保存在代码里,这样维护起来更方便,只是没法动态调整了;
- 为了展示更清晰,除了 Role,其它层都设计为了树形结构。为了简化连表查询和权限验证时的匹配规则,各层之间可以只在叶子结点进行关联,非叶子结点仅用于树形展示和选择,只是这样就没法实现在新增功能时自动赋给现有 Role,需要手动添加;
示例数据
Role
id | team_id | order | name | description |
1 | null | 1 | TEAM_OWNER | 团队拥有者 |
2 | null | 2 | TEAM_ADMIN | 团队管理员 |
3 | null | 3 | TEAM_MEMBER | 团队成员 |
Function
id | parent_id | order | name | description |
1 | null | 1 | 功能权限 | |
2 | 1 | 1 | 数据集功能 | |
3 | 2 | 1 | 数据集创建 | |
4 | 2 | 2 | 数据集修改 | |
5 | 2 | 3 | 数据集查看 | |
6 | 2 | 4 | 数据集删除 | |
7 | null | 2 | 管理权限 | |
8 | 7 | 1 | 团队成员管理 | |
9 | 8 | 1 | 团队成员邀请 | |
10 | 8 | 2 | 团队成员移除 |
Permission
id | parent_id | order | name | description |
1 | null | 1 | dataset:* | 数据集模块 |
2 | 1 | 1 | dataset:dataset:* | 数据集功能 |
3 | 2 | 1 | dataset:dataset:create | 数据集创建 |
4 | 2 | 2 | dataset:dataset:edit | 数据集修改 |
5 | 2 | 3 | dataset:dataset:view | 数据集查看 |
6 | 2 | 4 | dataset:dataset:delete | 数据集删除 |
7 | 1 | 2 | dataset:data:* | 数据功能 |
8 | 7 | 1 | dataset:data:upload | 数据上传 |
9 | 7 | 2 | dataset:data:delete | 数据删除 |
10 | 1 | 3 | dataset:ontology:* | 本体功能 |
11 | 10 | 1 | dataset:ontology:create | Class 创建 |
12 | 10 | 2 | dataset:ontology:delete | Class 删除 |
Api
id | parent_id | order | path | description |
1 | null | 1 | null | 数据集微服务 |
2 | 1 | 1 | null | 数据集模块 |
3 | 2 | 1 | /dataset/dataset/create | 数据集创建 |
4 | 2 | 2 | /dataset/dataset/edit/{1} | 数据集修改 |
5 | 2 | 3 | /dataset/dataset/list | 数据集列表 |
6 | 2 | 4 | /dataset/dataset/info/{1} | 数据集查看 |
7 | 2 | 5 | /dataset/dataset/delete/{1} | 数据集删除 |
功能权限 vs. 数据权限
场景
Team 内分为 Admin 和 Worker 两种角色,Worker 能查看 Team 下的所有 Datasets,以及分配给他的 Tasks,Admin 在 Worker 基础上增加了 Dataset 和 Task 的管理权限。分配某个 Task 给某个 Worker 时,会指定该 Worker 在该 Task 内的角色,可以是 Manager 或 Member,Memeber 只能执行普通操作,而 Manager 还能执行更高级的操作。
方案
- Team Worker 相关功能通过数据权限来限制即可,无需限制功能,功能都是开放的;
- Team Admin 相关功能要管理所有数据,与具体数据无关,因此无需数据权限,但需要通过功能权限来限制;
- Task Manager 比较特殊,这里需要结合数据权限和功能权限,首先用户需要有某个 Task 的数据权限(分配了该 Task)才能看到和进入该 Task,然后再依据用户在该 Task 内的角色是否为 Manager 来决定是否允许执行管理相关操作;
- 最好不要让 Team Admin 直接包含 Task Manager 的所有权限,如果某个 Team Admin 需要管理某个 Task,可以显示为其赋予该 Task 的 Manager 权限,这样权限更加清晰。但 Team Admin 可以执行与具体某个 Task 无关的全局管理操作,比如新建 Task、统计分析等;