1 基于角色的权限设计(RBAC)
目的:判断用户能否操作资源
表现:用户、角色、资源之间的连线
- 用户,特指“人”。真实存在的个体
- 资源,用户可以操作的接口、菜单、按钮、文件、图片等,都是资源
- 角色,抽象的层次很高。简单的场景,“用户组”也可以表达这个模块的含义
但,角色很复杂的:
约束类型 | 释义 | 举例 |
---|---|---|
自由组合 | 一个人可以同时拥有任意个角色 | 身兼多职 |
继承 | 拥有parent node,自动获得All children roles 的权限 | 中国家长 |
互斥 | 互斥的角色,一个用户只能拥有其中的一个 | 男、女 |
运行时互斥 | 给一个用户配置多个角色,但,运行时,只能激活一个 | QQ、360 |
限定个数 | 一个角色,只能授予有限的用户 | 主席 |
按顺序演变 | 婴儿、儿童、少年... | |
运行时计算 | 判断操作员是否资源持有者的上级 |
2 常见的设计方式
类别 | 示例 |
---|---|
层级 | 论坛系统,超级管理员、普通管理员、版主等,上级拥有下级的所有权限 |
角色 | 超市管理系统,收银员、开票员、仓管员等,用户之间的地位是平等的,分别对应不同的应用模块 |
资源 | 博客系统,文章的增删改查等操作,都是资源 |
流程 | 公文系统,基层科员起草-->科长审批-->办公室校对-->局长签发。如果公文不在当前用户的环节,即使是局长也无权修改 |
分析:
- 层级和角色,都可以抽象为“角色”
- 在流程中,不能把“公文”看做“资源”,节点才是。节点(资源)-->角色-->用户,串起来就是权限
一句话:用户、角色、资源,很灵活的权限结构
2.1 脆弱的策略
有了好的架构,不代表落地的代码也是灵活的。
例如,判断一个用户是否能查看项目报表,你的代码或许是这样的:
代码 |
---|
如果,新增一个角色,似乎,整个流程都崩溃了。当然,你可以加if...else
2.2 改进语义
还是上面的示例,换个方式呢,你的代码或许是这样的:
代码 |
---|
释义 |
---|
是否允许当前用户查看id=12345的报表? |
这样的描述,我觉得,更符合“人”的思维习惯。
推导的内容,仅供参考。你也可以得出自己的结论。我相信,就算用了框架,也会有各种不舒服。既然如此,从一开始,就抱着怀疑的态度,用实践去求证。
代码不会骗你。最终,不但要用对框架,还要改进
3 微服务与权限
微服务的架构,前后端完全分离。那么,在这种特定场景里,权限是怎么串起来的呢?
无论何时,权限,始终关心的是用户可以操作哪些资源:
- 推荐使用swagger2管理后台的接口,直观、易测试
- 推荐使用react、angular等框架自定义“组件”
3.1 后台接口&可读&可写
swagger2开放的API后台开放的接口(API),需要指定http method。比如,Create,是write;Get,是read。
创建接口的时候,读或写的权限就确定了。所以,读、写,是不同的接口(资源)。
接口(资源)-->角色 --> 用户,于是,权限,又串起来了。
3.2 角色
-
管理员、子账号,都是特殊的角色,他们都有操作后台的权限
-
收银员、开票员、仓管员等,都跟特定的业务有关系
在具体的场景中,你需要给某些模块命名。这时候,如果,你觉得不管是“用户”还是“资源”,都不适合,不妨用“角色”的概念封装这些模块。
下面,着重分析下“动态角色”(先这么叫吧。实现的时候要用到算法,我觉得有点难。所以,也没写全)
假设,“资源”属于currentUser,操作员operator的角色是哪个呢?
这些“角色”,都是动态的!!!怎么办呢?
- User表设计成树形结构
id/pid/level,分别对应id、父节点id、层级深度。于是,根节点={1,null,1},一级子节点={2,1,2}... - 访问接口的时候,动态判断操作员的角色
select case (level - ${operator.level})
when 1 then "immediateSubordinate"
when -1 then "immediateSuperior"
end as role
from t_user where id = ${currentUser.id}
动态查询operator‘s role,底层是在select tree node:
- 传统的树形结构id&pid,查询的时候,会用到递归。层级越深,效率越差。递归的SQL太复杂了,我没写
- 推荐使用lft&rgt,左右值的预排列算法。查询的性能非常好。但,算法的优化,没有固定套路,得自己写算法
文章一开始介绍了很多种角色,如果你恰好遇到了,不妨自己推导下。
3.3 前端菜单
组件的属性:id/pid/name/接口地址/参数model等,都是前端自己定义的。所以,这块的权限,要让前端自己配置、维护。
- id不要自动生成。这里的id,特指组件的id。可以借助数据库的唯一约束,让id是唯一的
- 前端的代码里,find组件by id。当然,你也可以使用有意义的name,不过呢,你怎么保证name不会重复呢?然后,很自然地调用错误的组件?没有必要吧
-
配置(前端自己配置。后端需要提供配置的表、逻辑)
组件 --> 角色 --> 用户
-
权限控制的流程
- 后端需要提供配置的表、逻辑
- 前端自己配置所有的组件:菜单、按钮、链接、文件等
- 用户登录后,访问后台的API,返回配置的组件列表(tree)
- 前端,根据返回的“组件tree”,动态展示
- 前端访问后台接口的时候,带上操作员的token
- 后端判断“操作员”能否操作资源
3.4 总结
或许,干聊流程,你觉得晕。来点实在的呗:
数据库建模相比数据库建模,我更加推荐DDD:分析业务流程、推演各个模块,最后,再考虑配置、存储
一句话:前端“组件”的权限,由前端负责;最严格的权限校验,还是要在后端做。
网友评论
另外,GitHub上的代码和文档,结合spring security的资料,分步骤实现了这篇文章中的观点。数据库/缓存/服务注册与发现/负载均衡等,代码里都没有。首先,这些功能,只要用过,都差不多;如果没用过,加在代码里,也看不懂。其次,service mesh已经将这些功能都挪到sidecar了,换句话说,运维自己会搞定的,开发人员不需要管