权限系统设计

2020-03-21

好多权限系统都设计得过于复杂,就这个话题写一写。

用户

有些系统要设计一个超级管理员用户,这个用户跟其它人不一样。比如有人觉得数据库里面应该有管理员用户和普通用户,它们是两种不同的用户,再或者操作系统里面的 root 用户。在我看来这个设计很奇怪。

奇怪的点在于,我们写代码实现时,是不是有弄一个跟其它用户不一样的东西,我们是否要实现出两种用户来,又或者,特殊写某种代码逻辑,比如说 username 为 root 的时候行为不一样,然后代码里写死。这是一个很丑很丑的事情。为什么叫 root 的用户就跟其它人不一样了呢?

设想一个场景,我们做了一个数据库,这个系统有它自己的权限系统。然后有一天,我们要把这个东西放到云上面,到以服务形式开放给用户。相当于数据库托管在云上,我们要不要在原本的管理员用户之上,再弄一个更高级别的超级超级管理员出来?再分别对待普通用户,管理员用户,超级后台用户?管理员用户是否能看到有一个比他更高经的存在呢?然后,既然他是管理员,应该能管理其它账号,那他能不能把超级后台用户给删掉呀?

如果按用户的特殊性来划分权限系统,在我看是一个很失败的设计,只有猴子才会设计这样的系统。

权限项

执行一些操作,需要检查哪些权限。这个权限就是权限项的概念。比如说执行读操作需要 read 权限项,执行写操作需要 write 权限项,创建需要 create 权限项等等。

注意,一个用户有哪些权限项,跟一个操作要检查哪些权限项,这个是两个正交的事情。这句话很重要,可以重复三遍:

一个用户有哪些权限项,跟一个操作要检查哪些权限项,这个是两个正交的事情

一个用户有哪些权限项,跟一个操作要检查哪些权限项,这个是两个正交的事情

一个用户有哪些权限项,跟一个操作要检查哪些权限项,这个是两个正交的事情

虽然最后的结果是,用户能或者不能执行某个操作;但是,理解这是两个正交的概念非常重要。这一步把事情分成了两层,做了一个解耦。

这样定义之后,整个系统的设计里面,就没有什么超级用户的东西。只是正好有这么一个用户,它正好有所有的权限项,而这个用户的 ID 正好叫 root。 如果有另外一个用户,它也是有所有权限的,那它也算是一个超级用户了。用户本来就没有什么特殊性,只是拥有的权限项不一样。

正好有很多重要的操作,都需要用到某一条权限项,这条权限项正好叫 super。拥有 super 权限项并不是一个特殊的用户,也许一个用户可以 super 权限,但是却没有基本的 read write 等权限。

所以呢,root,super 等等这些名词都不是特殊的东西。就是某一个用户,某一项权限而已。

授权的概念

简单讲,就是一个用户可以把自己拥有的权限,给另外一个用户。

授权也是一种操作,所以能执行这个操作也是要求有某项权限的。

如果一个叫 root 的用户,它自己没有 super 权限,它就没法给其它人 super 权限。

如果一个叫 dbaas 的用户,它有 super 权限并且能执行授权,它可以把这个权限给 root 用户。

如果一个叫 xxx 的用户,它有 super 权限,但是它没有执行授权操作的权限,它是不能把自己的 super 权限给其它用户的。

登陆

登陆是用一个用户密码,验证是谁进入系统了。其实登陆过程除了认证该用户是谁,就没有其它作用了。

这里面有很多小细节,关于怎么样认证。最 SB 的方式当然是用数据库存明文密码,认证需要有一个地方存一些用户的信息,用明文存的话很就很大的泄漏,所以要加密存储。在 MySQL 里面,这个信息是加密以后存储在 mysql.user 这张表里面的,是一个单向加密过程,也就是用原始密码可以很容易计算出加密后的值,但是拿加密后的值很难解出原始的密码。

对于开源系统,代码都是公开的,怎么样加密,用的什么算法这些都是所有人可以看到的。所以如果拿到了加密后的数据,暴力破解的方式,撞库也是能撞的,不能说完全没有风险。 所以更鬼畜的方式是,这块可以用插件或者用其它形式实现。让用户自己来决定怎么实现认证过程,这样别人连使用的什么算法都不知道,就更安全了。

其实在我看来数据库本身并不需要一定包含这一块,尤其是开源的实现里面,做了别人还不一定信任。我想强调的点是,这个过程只是让系统知道是谁登进来了。 完全可以用特殊的方式登陆,比如插件或者登陆协议。一旦用这种方式登陆上来的,就知道是一个虚拟的用户进来了。

如果用户是做到系统外面,在 mysql.user 表里面并不需要一定有这个用户。像 sso 单点登陆就是把认证过程做到系统外部去的。

注意,这个登陆用到的密码,或者是 key 什么的,全部是给掌握在用户那边的。这样子云上面的用户可以信任,因为后台管理者也是无法偷数据的。

更进一步,可以把 “用户有哪些权限项” 整个事情都做到系统的外部。只有 “哪些操作检查哪些权限项” 是做到权限系统内部的

其它

关于用户和权限项,关于登陆过程的想法都写了,其实想说的已经说完了。最后扯一点点对一些实际的例子的看法。

关于 RBAC 的,RBAC 是基于角色的权限控制。一个用户可以拥有不同角色(role),每一种角色会有一类的权限。然后授权的时候,可以为用户设置不同 role,这个用户就能执行该 role 对应的所有操作。 比如 role 是一个程序员,或者 role 是一个数据库管理员,他们看到的后台数据是不一样的。拿回权限很方便,比如某个员工,岗位调动或者离职了,就收回它的 role,这要比对他拥有的权限一项一项删除来得容易。

我认为基于角色的划分并不好,不太灵活。它相当于某一类 role 可以执行哪些操作,然后某一个用户是哪些 role。因为比起基于权限项的粒度划分,基于 role 是很容易不正交的。

关于 IP 白名单的,有些用户有个需求是,只有自己内网机器才可以连接数据库,然后希望数据库里面做一层白名单的过滤。这个完全应该移到数据库系统外面去做。怎么连接怎么认证这些,并不是属于数据库本身应该关注的内容。

对于 unix 权限系统的看法。unix 是一个多用户的设计,它会有一个文件“是谁的”这样的概念,然后是用户,组,其它这样分类,每类角色再对应到不同的权限,这个复杂性就很高。分组之后,又会有管理员的组。如果资源没有“是谁的”这样的概念,我觉得可能会简化很多。

privilege