权限管理系统设计的一点理解

权限管理要回答的终极问题是某人对某资源是否有某权限。

围绕这个问题,最简单的方法就是直接记录某人对某资源是否有某权限,如果查询到有,那么答案就是是,如果没有那么答案就是否。

但是如果权限分的非常细致,可能的权限很多,那么某人对某资源的拥有的每一个权限都要记录一下,这样就要记录的行就太多了,要解决这个问题,可以对一些固定一起出现的权限进行合并,比如,要对某个资源进行改写,起码要有读取的权限,那么读和写可以合并为一个读写权限,这时在用户拥有写权限的时候就不需要额外记录一个读权限了。

通常我们会把对资源的权限抽象为增删改查,但是我们依然不能阻止有些情况下我们需要将权限更加细分,比如某人对某资源有读权限,同时有排序的权限,但是没有改写的权限,或者某人只能改写某资源的某一部分等等,这样细分下去,就会产生非常多的权限,如果在按照上面的方式对权限进行合并,排列组合就会产生更多的权限。这样就会使权限描述变得更加复杂,编程的时候要面对各种各样的权限。要解决这个问题,我们可以添加权限组的概念,首先梳理清楚某人对某资源存在的所有的基本权限。然后根据应用场景,要执行某一请求,则这个人必须拥有某些权限,然后将这些权限合并为权限组,编程的时候不需要管到底有哪些基本权限,只需要考虑在哪种应用场景下某人需要对某资源有哪个权限组。

当然,我们不希望用户可以拥有的权限那么复杂,我们先假设只需要记录几个权限,比如增删改查。但是我们依然需要为某个用户对应的每个资源进行记录,如果我们想要复制某人对应的多个资源的所有权限给另外一个人。比如某个人拥有编辑某些大量的文章的权限,现在给他安排了一个合作者,需要拷贝前者拥有的所有可以编辑的文章的权限给后者,我们就需要拷贝所有的记录给后者,这样需要进行大量的拷贝。如果这种情况时常发生,那么仅仅记录某个人对某个资源拥有某种权限的方法就会显得很繁琐。这种情况下,我们可以创建一个资源组,将某个人拥有的某些有共同特征的资源添加到一个组中,不再记录这个人对某个资源的权限,而是记录这个人对应这个组的权限。他对这个组有什么权限,就对组中的成员拥有什么权限。当我们给他安排一个合作者的时候,只需要添加后者对这个组拥有某些权限就可以了。

但是如果我们要复制权限的资源并不是很多,复制一次权限只需要添加几条记录的时候,我们不需要添加资源组。在这种情况下还会发生另外一种困难,如果很多人对某几个资源拥有共同的权限,比如我们希望100个人组成一个小组,他们共同维护几个资源,并且这个小组的成员很不稳定,经常会发生人员变动,那么每次发生人员变动都要修改变动的人对应这几个资源的权限记录。 或者这些人对应这几个资源的权限经常发生变化,每次发生变化都要修改每个人对应资源的权限记录。为了减少每次改动记录的条数,我们可以创建一个用户组,把这些人添加到一个用户组中,不记录每个人对应每个资源的权限,而是记录这个用户组对应这几个资源的权限。每次发生人员变动的时候只需要将发生变化的人添加到用户组或者移除用户组。

极端一些,如果有一大群人管理一大堆资源,而且他们对应每个资源的权限都是一样的,那么我们可以既使用资源组又使用用户组,当人员发生变化的时候就把人员添加或者移出用户组,当资源发生变化的时候就将资源添加或者移出资源组,当权限发生变化的时候我们直接修改用户组对应的资源组的权限就可以了。甚至我们还可以同时使用权限组,可以设计很多细分的权限,然后将这些权限分到各种各样的权限组,用户组和资源组之间的关系靠权限组关联。而某个人对应某个资源的权限使用某个用户对应某个资源的某个权限进行特殊处理。相信你看到这里一定觉得这个系统太复杂了,不容易维护,而且每次鉴权都要考虑很多种情况,某个人对应某个资源的某个权限,可以来自自身,可能来自用户组,可能来自资源组,可能来自权限组。

当然同时使用用户组和资源组的时候还有一种异常简单的情况,只需要判断某类人是否拥有某类资源的某个权限。比如所有的学生都拥有进入教室和在教室学习的权利,而不关心他们具体是谁,在哪个位置听课。如果一个系统中的所有权利赋予都是这种情况,我们就不需要单独记录某个人对应某个资源拥有某个权限了,而是直接将用户添加到用户组,资源添加到资源组中,然后直接关联用户组和资源组就可以了。

当然还有另外一种例外,那就是可以有用户组,也可以没有用户组,可以拥有资源组,也可以没有资源组,权利联系的双方具体是什么没有关系,只要他们符合某种条件就可以了,casbin就是这样的情况,只需要判断左右值是否符合条件,如果两边都符合条件,那么这个人对他请求的这个资源就拥有记录中的权限。然后通过符合条件的索引获得的资源一定是这个人拥有权限的资源。符合条件可能是因为他是某个用户组的成员,也可能是因为他就是条件中的唯一的一个人,而资源可能是因为它属于某个资源组,也可能是因为他就是条件中唯一的资源。

如果你要为某一类人创建对某一文件的某一权限,那么你就需要让代表这一类人的字符串有唯一的共同性。如果你要给某人某一类资源的某一权限,那么你就需要代表这一类资源的字符串有唯一的共同性。当然你也可以任性的记录某一个人对应某一个资源有某一权限,只要这个人的索引和这个资源的索引是唯一的就可以了,但是这样需要记录的权限条数可能会很多。

这样的设计特别适合用户和资源有层级关系的系统,比如某个公司使用某个文件系统。我们通过某部分的某某名字描述一个人,通过文件路径描述文件。那么部门就是用户组,目录就是资源组,这样用户的索引和文件的索引都是唯一的,而且表明了他们之间的关系。

当然没有层级关系的情况依然可以适用这套权限管理系统,只不过限制不在是在索引中,而是需要手动添加,比如,检索某个名字为name的文件,然后再url参数中添加作者为user,当对作者为user名字为name的文件拥有读权限的用户检索的时候,casbin就会放行,系统检索出所有名字为name,作者为user的文件返回,这样用户就获得了他想要的作者为user名字为name的文件,也因为他拥有对应的权限,他才顺利拿到了文件。

关于使用用户-资源组-权限组方式管理权限的分析

比如 A用户拥有1资源修改权限

权限管理的终极问题是 某个人是否有某个资源的某个权限
按照之前的理论 如果资源往往不是单独出现的,那么我们可以使用资源组的概念进行包装。

上面的描述就变成了

1资源属于 一资源组


A用户拥有一资源组修改1资源的权限 

这样的描述可以解除上面的描述

而A用户对一资源组不可能仅仅拥有几个权限,他可以拥有修改1资源的权限,也可能拥有修改2资源的权限,那么我们可以把这些权限合并到一个权限组中。

上面的描述就变成了


1资源属于一资源组
修改1资源的权限属于管理员权限组
A用户拥有一资源组管理员权限组

上面3条描述同样可以接触第一个答案。

然后某个人对某个资源是否有某个权限的问题就变成了

某资源是否属于某资源组

某权限是否属于某资源组

某人是否拥有某资源组的某权限组

这样虽然看起来更加繁琐了,但是在权限转让和资源变动的时候不需要像第一种描述那样,每个人对应每个资源的每个权限都要变动,只需要修改某人拥有的权限组或者某资源是否属于某资源组就可以了,谁变动修改谁,而不是一个环节变动其他环节都要跟着变。

这种用户-资源组-权限组的描述是实际上生活中很常见的。比如,小明是发财公司的网络管理员。小明就是用户,发财公司是资源组,网络管理员是权限组。

这个时候有人可能会觉得网络管理员是用户组而不是权限组,这种想法实际上是不对的。用户组的描述应该是 小明属于发财公司的网络管理员组。这两种描述的内涵是有区别的。