老开发眼里的权限设计

1 基于角色的权限设计(RBAC)

目的:判断用户能否操作资源
表现:用户、角色、资源之间的连线

老开发眼里的权限设计_第1张图片
users-roles-resources
  • 用户,特指“人”。真实存在的个体
  • 资源,用户可以操作的接口、菜单、按钮、文件、图片等,都是资源
  • 角色,抽象的层次很高。简单的场景,“用户组”也可以表达这个模块的含义

但,角色很复杂的:

约束类型 释义 举例
自由组合 一个人可以同时拥有任意个角色 身兼多职
继承 拥有parent node,自动获得All children roles 的权限 中国家长
互斥 互斥的角色,一个用户只能拥有其中的一个 男、女
运行时互斥 给一个用户配置多个角色,但,运行时,只能激活一个 QQ、360
限定个数 一个角色,只能授予有限的用户 主席
按顺序演变 婴儿、儿童、少年...
运行时计算 判断操作员是否资源持有者的上级

2 常见的设计方式

类别 示例
层级 论坛系统,超级管理员、普通管理员、版主等,上级拥有下级的所有权限
角色 超市管理系统,收银员、开票员、仓管员等,用户之间的地位是平等的,分别对应不同的应用模块
操作 博客系统,文章的增删改查等操作,都是资源
流程 公文系统,基层科员起草-->科长审批-->办公室校对-->局长签发。如果公文不在当前用户的环节,即使是局长也无权修改

分析:

  • 层级和角色,都可以抽象为“角色”
  • 在流程中,不能把“公文”看做“资源”,节点才是。节点(资源)-->角色-->用户,串起来就是权限

一句话:用户、角色、资源,很灵活的权限结构

2.1 脆弱的策略

有了好的架构,不代表落地的代码也是灵活的。
例如,判断一个用户是否能查看项目报表,你的代码或许是这样的:

代码
老开发眼里的权限设计_第2张图片

如果,新增一个角色,似乎,整个流程都崩溃了。当然,你可以加if...else

2.2 改进语义

还是上面的示例,换个方式呢,你的代码或许是这样的:

代码
老开发眼里的权限设计_第3张图片
释义
是否允许当前用户查看id=12345的报表?

这样的描述,我觉得,更符合“人”的思维习惯。

2.3 总结

判断权限的代码,可以写死在代码里,也可以放到配置文件、annotation、或是抽取到"service/util"里。无所谓啦。
我呢,还是建议采用“改进的语义”。只要流程确定好了,配套的验证框架,你自己也会持续改进地嘛。即便写死在代码里,也是进步哦,毕竟很明确嘛。

推导的内容,仅供参考。你也可以得出自己的结论。我相信,就算用了框架,也会有各种不舒服。既然如此,从一开始,就抱着怀疑的态度,用实践去求证。
代码不会骗你。最终,不但要用对框架,还要改进

3 微服务与权限

微服务的架构,前后端完全分离。那么,在这种特定场景里,权限是怎么串起来的呢?
无论何时,权限,始终关心的是用户可以操作哪些资源:

  • 推荐使用swagger2管理后台的接口,直观、易测试
  • 推荐使用react、angular等框架自定义“组件”

3.1 后台接口&可读&可写

后台开放的接口(API),需要指定http method。比如,Create,是write;Get,是read。

创建接口的时候,读或写的权限就确定了。所以,读、写,是不同的接口(资源)。

接口(资源)-->角色 --> 用户,于是,权限,又串起来了。

3.2 角色

  • 管理员、子账号,都是特殊的角色,他们都有操作后台的权限

  • 收银员、开票员、仓管员等,都跟特定的业务有关系

在具体的场景中,你需要给某些模块命名。这时候,如果,你觉得不管是“用户”还是“资源”,都不适合,不妨用“角色”的概念封装这些模块。
下面,着重分析下“动态角色”(先这么叫吧。实现的时候要用到算法,我觉得有点难。所以,也没写全)
假设,“资源”属于currentUser,操作员operator的角色是哪个呢?

老开发眼里的权限设计_第4张图片
层级关系

这些“角色”,都是动态的!!!怎么办呢?

  • 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 总结

或许,干聊流程,你觉得晕。来点实在的呗:

老开发眼里的权限设计_第5张图片
数据库建模

相比数据库建模,我更加推荐DDD:分析业务流程、推演各个模块,最后,再考虑配置、存储

一句话:前端“组件”的权限,由前端负责;最严格的权限校验,还是要在后端做。

4 spring security实战

源码
gitHub上的代码和文档,结合spring security的资料,分步骤实现了这篇文章中的观点。
数据库/缓存/服务注册与发现/负载均衡等,代码里都没有。因为,service mesh已经将这些功能都挪到sidecar里了,换句话说,运维自己会搞定的,开发人员不需要管

你可能感兴趣的:(老开发眼里的权限设计)