天天看点

web权限管理设计(1)——设计的要点分析(2)

接着上文,分析到了继承给系统带来的困难。

就目前而言,我想不到能够完美解决问题的方案,但是我认为还是不能抛弃继承。因为他确实能给管理者减少很大的工作量。

程序的一大好处就是代替用户做重复性工作,显然这里给管理者减少工作量就是一个很好的方向,哪怕这个程序执行的慢一点,那也比人工好很多。

再回到上文说的,权限管理系统该有什么的特点:

  1. 管理者能够清晰的对权限进行管理,也就是那个管理界面,是符合正常人的思维的。
  2. 权限校验的性能要高,不能因为这个对整个系统的性能产生太大的影响

管理界面

假设我们是给一个拥有10万员工的超大型公司设计权限管理系统,那么我估摸着大概会有1000个角色。

那么如果不使用继承,那么我们的角色管理界面,就是一个表格[角色id,角色名,资源列表],那么这个列表就会有1000条数据,并且仅能通过名字判断它大概是给谁用的,因为资源列表就是几十个上百个资源id,用逗号分隔,根本无法通过资源列表去判断。

而使用了继承,可能会导致角色数量增加,因为为了更好的继承,角色的设计往往需要更细致。

但是有了继承数据,就可以生成一个链表图或者别的,让他们的关系更明显。当然这个难度有点大。

退一步讲,即使为了简单,界面依然是个普通列表:[角色id,角色名,继承角色,资源列表],这个列表也还是和没有继承时那么难看,难用。但是,继承并没有给管理界面带来什么新的问题,还带来可一个优化的方向,尽管这个方向比较难实现。

确实,就目前的想法而言,继承不能让界面更合理更好看,但是也没有让界面变得更难看。

性能

一个系统对性能的考虑,一般最优先考虑用户。因为他们才是使用主体,而对于管理员,慢点好像也还能接受。毕竟他们的数量和用户相比,太少了。

查询

那么权限管理系统与用户最相关的就是每次访问时的鉴权。也就是权限的查询。

上一篇文章里分析了,ACL最直接,查询性能极高。但是这不现实。RBAC的出现会让权限的查询绕弯弯。但是这才符合现实。

前面我们设计了对用户的授权只能使用角色组,而角色组不能直接拥有资源,得通过角色去拥有,以至于查询权限多了很多个环节。

对于一个千万用户的系统,通过角色和角色组的封装,可能最终的角色组的数量仅有1000个。如果我把这些角色组加载到redis里去(1000个key也不算多嘛),而不是放在数据库中,那么,查询的时候是不是就快了很多了?

但是前面的设计思路中,角色组不能直接拥有资源,我们查询到用户所在的角色组之后,依然要去查询对应的角色的权限,难道我再去数据库里查?那把角色组放到redis的意义就不大了。

要么,把角色也加载到redis中,那么redis中key的数量就太多了。不理想。所以,上述方案按照原来的设计思路行不通。

为了加快查询的效率,我考虑让角色组直接拥有资源。那么,前面的分析的东西不就白费了?其实不是的。前面分析的理论,是设计系统的基础,权限系统的设计依然按照上述思路去设计。我仅仅是让角色组增加一个字段:资源。对,就是角色组通过角色获取到的所有资源。

这个资源字段的出现,仅仅是为了加快查询的效率。这样查询一个用户是否具有访问某个资源的权限,只需要:

  1. 查询当前访问的资源的id
  2. 去数据库查询其所属的角色组,可以有多个
  3. 然后去查询这些角色组的资源字段里是否包含这个资源,只要有一个角色组包含,该用户就可以访问该资源。

最后一步因为是在redis中查询,所以性能会高很多。而前两步,不管怎么样都需要(当然用别的方案优化了也可以都不需要)。

角色组的这个资源字段是系统根据所拥有的角色生成,连系统管理员都无感知,也不能直接修改它。想修改它,只能通过修改角色组的角色,所以,前面讲的所有理论依然存在且被使用。

PS:在实际的系统设计中,还有更细节的优化,这里就不展开,后面再讲。

增删改

这三个操作仅有管理员可以使用,所以,这部分的性能,慢点就慢点吧。让管理员忍忍,尽量保证一个操作能在三秒内完成,应该在可以接受的范围内。

“增”对已有的角色和角色组不造成直接影响,所以没什么问题,这里不再分析。主要是删和改。

为了优化查询性能,新增了用户组的资源字段,就因为这个字段的存在,让删和改变得复杂了许多。

举个例子,角色组A,包含了角色(1,2,3,4),而角色3又继承自角色5。那么我修改或者删除角色5,就会影响到角色组A,所以就得更新角色组A的资源字段。

更要命的是,我并不知道角色5会影响到角色组A,因为它并不直接拥有角色5。那么该如何捕获这种变化呢?

说实话,想不到什么好方法,因为根本无法确定哪些上级角色和角色组会受到角色5的影响。下面再细致点分析这些变化。

分三种:

  1. 删角色组,因为规定只要角色组及其下级只要还存在成员,就不允许删除,所以能被删除的角色组,就不会影响其他角色组和角色。
  2. 删角色,首先,会影响到直接继承自该角色的角色,这个好办,直接搜索谁的继承角色id是被删除的角色id,就可以确定了。 其次,会影响到直接或间接包含了该角色的角色组。
  3. 直接删除某个资源,首先查询角色里谁包含这个资源,可以直接删除;其次,角色组也需要查询资源字段里是否有该资源,也可以直接删除。

分四种:

  1. 修改角色组包含的角色,这个比较好办,直接用修改后的角色计算资源字段即可
  2. 修改角色包含的资源,这会影响角色组,不会影响其他角色。
  3. 修改角色的继承关系,分为取消继承和修改继承。对角色不会有影响,但是对某些角色组会影响。
  4. 修改资源的名字啥的,不影响,因为是使用id关联。id是系统生成,不允许手动修改
改进方案

上面的麻烦均由于角色组增加了资源这个字段引起的。本身不管增删改都不需要操心。因为都是动态查询的。

但是现在为了更新这个资源字段,不得不捕捉这些更新。

所以首要判断哪些角色组会受到影响,最开始的方案是:每一个角色组的每个角色,都层层遍历下去,查询是否与这个变动的角色相关,如果相关,那么,就要重新计算这个资源字段。对,没错,几乎就是要全局遍历,最要命的是角色层层继承,整个搜索深度很深。所以性能很差。

我想到的一个小小的优化是:实际上任何会影响到角色组的资源字段的变化,本质上都是某些资源的变更。变更的方向就两个,增加资源和减少资源:

  1. 对于增加资源,如果这个增加的资源,某角色组本身就已经有了,那么这个资源的增加对该角色组无影响。
  2. 对于减少的资源,如果这个角色组本身就没有该资源,那么这个资源的减少对该角色组无影响。

所以,角色组资源字段更新的几个步骤:

  1. 分析一次变更的资源,增加了哪些,减少了哪些
  2. 所有角色组的资源字段,都按照上面的分析思路进行对比,这些资源的增加和减少是否有可能影响。只要判断出哪个资源可能影响到了该角色组,那么直接停止分析,直接对该角色组的资源字段进行完全更新。
  3. 如果一个角色组的经受了上面所有增减资源的考验,那就说明它不需要变更。

比如说,我删除了角色5本身拥有的资源001,这个修改,不会对其他角色造成影响,所以不需要去遍历角色。但是角色组就需要遍历了,遍历的目的就是确认资源里是否拥有资源001,对于拥有资源001的角色组,不犹豫,直接重新计算一遍资源。对于没有资源001的角色组,不需要更新,因为如果它直接或间接继承了角色5,那么它一定拥有资源001。在详细一点缕清这个关系:

  1. 某角色组没有资源001,是该角色组与角色5没有关系的充分必要条件
  2. 然而, 某角色组拥有资源001,是该角色组与角色5有关系的必要不充分条件

这样做的好处是避免角色组进行全局深度搜索。

当然,上面思路我还没实践,仅仅是理论分析能优化性能。退一万步讲,哪怕我每一次更新都对角色组做全局更新,对于大多数系统的那点数据量,也不是什么大问题。你说你系统用户量上百万,那么,请花钱请专业团队设计。

在之后的真正编写代码的环节中,肯定也会想到更多的优化方案。我有信心将所有的接口响应时间控制在3秒内。