核心模型
ACL (Access Control List)
每个资源上直接记录哪些用户可以做什么操作
这是最原始最直观的权限模型,结构是这样
|
|
最典型就是 Linux 文件权限 -rwxr-xr-x
如果用数据库实现 ACL,通常是这样设计
|
|
例如
|
|
ACL 的优点是直观和灵活,谁能做什么一目了然,可以精确到用户,资源,动作。缺点是权限爆炸,10000 用户*10000 资源,最终会表数据会异常庞大。
缺点是无法表达角色。
ACL 适合少数资源共享的场景,例如某个仓库或某个文件的读写权限,不适合中后台系统。
RBAC (Role Based Access Control)
用户不直接拥有权限,而是通过角色获取权限
这是中后台管理系统的常用模型,结构变成三层,用户 > 角色 > 权限
如果用数据库实现 ACL,通常是这样设计:
|
|
关系是
|
|
RBAC 的数据库设计几乎都是这 5 张表
|
|
RBAC 的优势
- 权限复用,不用给每个用户配权限,只需要给用户绑定角色,解决了 acl 的权限爆炸问题
- 权限管理集中,修改权限只需要修改角色
缺点
RBAC 只解决了接口权限,但是很多系统还有 数据权限,例如:
|
|
RBAC 无法表达,不能控制读取哪些数据 ,这是所有中后台最核心的设计问题。
接口权限 ≠ 数据权限,用户可以访问删除接口,但是不能删除别人的数据!
提问
下面两个接口设计哪个更好?
方案 A
|
|
方案 B
|
|
普通用户:
|
|
管理员:
|
|
你觉得哪个更好?
回答
大部分系统采用 B 方案,原因是权限属于业务逻辑不属于 API 设计,RESTful API 接口设计应该表达资源,而不是 角色,所以权限控制应该是 service 层。否则会接口爆炸,每种权限都要设计对应的接口。
那么什么时候用 A 呢? 业务语义不同,例如
|
|
/users/:id/orders 就是一种资源,但是 all_videos 不符合 RESTful 设计。不要因为权限而增加接口,根据业务语义设计接口。
举个栗子
一个后台系统,根据"运营",“管理” 需要展示不同的数据内容,这属于数据权限,一个接口足以。
一个用户系统一个后台系统,用户系统展示的是用户的子资源,后台系统展示的顶级资源,可以设计为两个接口,根据业务设计接口,不要根据数据权限设计接口。
还可能需要查询出相同的数据结构,但是同一个用户在不同的页面需要查询出不同的结果,在个人页面仅查询出自己的。
RBAC + 数据权限
很多系统不仅要解决能不能访问接口,还需要能看到哪些数据。
场景的模型权限
1. 仅自己
我的订单,我的视频,我的文章,sql 条件是 where user_id = ?
2. 部门权限
企业系统常见
|
|
权限
|
|
SQL
|
|
3. 组织树权限
大型公司
|
|
权限
|
|
能看
|
|
SQL
|
|
4. 自定义数据权限
例如
|
|
SQL
|
|
这种系统一般叫 ABAC。
互联网后台最常见的设计就是 RBAC+数据范围,例如角色表增加 data_scope 字段,参数例如
|
|
权限关系是
|
|
伪代码逻辑
|
|
这就是工业界最常见的方案 RBAC + data_scope
最佳实践
- api 层不处理数据权限
- service 层决定数据范围(决定规则)
- db 层执行 sql(执行规则)
通过 SQL 查询就过滤 WHERE user_id = ? 权限更安全,且 SQL 性能会更好。
菜单权限 VS 接口权限
很多中后台系统都会有菜单的控制,根据用户角色展示哪些菜单,例如"用户管理",“订单管理”,“系统设置” 等。
菜单权限要不要等于接口权限? 答案是否定的,这一个非常大的坑!
错误设计
数据库
|
|
然后
|
|
前端
|
|
问题来了!! 一个页面可能调用
|
|
假设系统有 200 个API,那权限就有 200 个! 角色配置就变成某个角色绑定 200 个相关接口,这些接口对于人类很难读懂,假如让项目经理或 ui 设计师来看,GET /videos,他们不知道这个接口是查看视频还是审核视频。
最佳实践
菜单权限 ≠ 接口权限,但菜单需要依赖接口权限。
UI 权限只是体验控制,不是安全控制。
权限分为三层
- 菜单权限
- 按钮权限
- 接口权限
结构
- 菜单权限 > 控制页面访问,例如
video_page - 按钮权限 > 控制按钮是否显示,例如
video_delete_button - 接口权限 > 后端 API 控制,例如
DELETE /videos/:id
为什么必须是三层?
前端权限只是 UI,可以被绕过,最终权限必须在后端,成熟的权限架构是
|
|
结构
|
|
数据库设计
-
用户表
-
角色表
-
用户角色关联表
-
权限表 permissions(id, code, name)
-
角色权限关联表
-
菜单表 menus(id,name, type, permission_code)
1 2 31 视频管理 page video_read 2 删除按钮 button video_delete 3 发布按钮 button video_publish -
api 权限映射表 api_permissions(id, method, path, permission_code)
1 2 3 4GET /videos video_read POST /videos video_create DELETE /videos/:id video_delete POST /videos/publish video_publish
流程
当用户请求 DELETE /videos/10
- 根据 api
DELETE /videos/10查询权限,得到video_delete - 找到用户角色
user - 判断用户角色
user权限是否有video_delete
权限有三种来源
- 角色直接绑定权限
- 角色拥有菜单 > 菜单绑定权限
- 特殊用户权限
通常取并集来判断一个用户是否有相关权限。
ABAC(Attribute Based Access Control)
RBAC 在中小系统非常好用,但是当系统规模变大时,RBAC 就会开始出现三个严重问题!!
- 权限爆炸,当系统发展一年后,500 个 API,50 个页面,200 个按钮,权限数量 800+,角色 100+,权限关系 5000+,这个时候就没人敢改动权限,改错一个就可能影响多个角色。
- 角色爆炸,现实业务通常是部门不同,地区不同,业务线不同,因为产生非常多的角色
- RBAC 只能表达有没有权限,不能表达在什么条件下有权限,比如运营可以删除视频,但条件是 只能删除自己创建的视频。
RBAC 解决的是谁能做什么,现实问题是,谁在什么条件下,对什么资源,做什么操作。
这时候就需要 ABAC,核心是 通过属性决定权限
一般会有一个 policy 引擎,例如规则
|
|
访问时
|
|
系统会读取
|
|
最后计算出
|
|
Go 比较流行的权限引擎有 Casbin,其规则是
|
|
多租户权限
多租户是很多 SaaS 系统的核心,模型是
|
|
数据结构是
|
|
|
|
|
|
所有的业务操作查询必须带上,否则就会出现 数据串租户
|
|
真正的高级问题
很多系统会遇到一个超级难的问题:
|
|
例如
|
|
SQL 会变成
|
|
这会让查询变得很复杂。