同一公司下多产品的用户权限管理系统设计

对于一个拥有多个产品,然而产品之间用户又可以统一登陆各个产品,只不过在不同产品间拥有不同权限。如果他没有买这个产品多license,那他登陆产品后无法使用。对于一个公司而言,如何管理好被授权用户(可能是一个公司购买了一个license,公司的所有员工都可以用)在不同产品下的权限,可不是一件简单的事。现在,我就来设计一个这样的系统。

对象设计

在这个系统里,会哪些基本的对象存在呢?它们又通过怎样的逻辑联系在一起呢?在这个系统里,我们需要明确它们之间最终在UI界面上如何展示。

产品-角色-权利

第一部分是找到一个用户在这个产品中的权限。我们首先需要在这个系统中创建一个产品,当然,系统中存在多少产品完全是公司产品线决定的,我们希望公司无论未来多少产品,这个系统都能满足产品权限控制的需要。

这个产品的用户将被赋予某种角色。而不同角色的用户得到的权利(entitlement)不同,因此,实际上对于用户而言,他拥有哪些权限,完全是因为他的角色而决定。

后文我们会讨论,entitlement并不需要我们这个系统来管理,因为不同的产品entitlement会有很多,而且数据格式也不一定,我们后面讨论。

当一个用户拥有多个角色的时候,他将得到这些角色权利的并集。当然,有的时候在一个产品里,某些权利是互斥的,也就是说同时拥有这两个权利会产生冲突,因此需要产品内部做进一步处理,但对于我们这套系统而言,并不需要考虑,我们要做的更通用一些。

许可证

许可证是用来卖的,一个产品上线之后,用户想使用它,需要得到公司的授权,通过一个许可证来得到使用的权利。而一个license可能包含一个或多个角色,拥有这个license的用户,就将得到这些角色对应权利的并集。

用户管理体系

我们大多数产品都尝试过开发自己都用户管理体系,将这些用户管理体系提炼出来,把它放到我们的这个系统里面。

我们要管理的这些用户,他们可能会购买license,也可能不会。有些产品可能并不需要license就能使用,只要这个用户存在于我们的系统里面。

另一种情况是,对于2B的产品而言,往往就有一个以整个公司(firm)为对象的管理单位,一个公司购买了一个license,那么它下面的所有用户都将拥有这个license都权利。还有一种设计是,一次性卖个这个公司特定数额都license,公司自己去分配,当这些license用完之后,你必须另外再买。这两种设计都是有的,不能只取其一。

因此,我将用户体系设计为“组织(公司或公益单位)-组(部门)-用户”。对于不同的产品而言,情况可能不同,有些产品仅卖给独立的用户,因此这些用户不存在三级组织关系。

管理员

管理员是登陆我们这个系统的用户,即可以把他们放置于上述用户管理体系内,也可以单独作为一个模块。如果把他们置于上述体系,那么相当于把我们这个系统也变成了我们这个系统里面的一个产品,这种自身包含自身的奇怪逻辑让系统设计非常难理解。因此,我们还是把管理员这个部分当作一个模块,管理员不是上述用户管理体系中的用户,而是本系统的用户,因此,这些管理员其实无法登陆本系统所管理的那些产品。

我们需要三层管理员:超级管理员,产品管理员,公司(组织、单位)管理员。超级管理员登陆之后,可以创建产品、公司、管理员,并且把管理员和产品、公司映射起来。产品管理员主要任务是管理自己的产品内的用户的权限怎么分配,比如那些公司购买了我的license,这些公司的角色,公司内的某些组的角色分配,甚至细到单个用户可以拥有哪些角色。而公司管理员则无法对产品相关进行操作,只能在有限的范围内为自己公司的组、用户进行角色分配和调整,而且,他只能看到自己购买的license所拥有的权限。

对象关系

上述这些对象,按照怎样的逻辑关联起来呢?它们是怎么相互作用,最终得以控制产品的权限的呢?

一个用户权限管理系统的设计示意图

理想的设计

上图展示了这个系统的理想设计。系统上线之后,由超级管理员创建产品、公司、对应的管理员。随后,产品管理员登陆系统,创建这个产品的role、entitlement和license,并且将某些license配置给公司或独立用户。再之后,公司管理员登陆,对自己公司的role进行逐一分配。这样,对于公司内的某个员工,或者独立用户,就可以使用自己的账号登陆这个产品,登陆之后,产品程序会从本系统中找到这个用户所拥有的权利集合,从而判断这个用户将拥有哪些操作权限。

在现有的一些设计中,产品管理员可以给公司配置一些原本卖给公司license之外的role,一旦这样做了,那么公司内的员工将继承这些role,当然,产品管理员可以对具体的某个用户进行配置,把某些特定的role从该用户的role集合中拿掉。

如果允许这样做,那么公司管理员和产品管理员的某些功能是重合的,这就会引起一些冲突。这需要仔细思考,公司管理员到底应该只拥有哪些操作权限,到底有没有分配权利的权限。

现实的考虑

但是在现实中,我们可能并不存在超级管理员这样的角色。我所在的公司通过一家第三方公司来售卖license,当和一家公司签约后,由这家第三方公司通过api把这个license写入到我们的数据库中。所以,产品管理员其实没有创建license和organization的权利,但是他需要维护一个license对应哪些role。

我们也没有在自己的系统里实现entitlement,因为它太庞大来。理想化的设计是将entitlement实现在我们的系统里,一个产品通过API,获取当前用户在当前产品下的所有权利,这样这个用户对产品的操作权限就一清二楚。但是我们需要考虑两个因素:1.一个产品的entitlement可能有上千个,而且这些entitlement都会被产品的源码使用,2.如果将entitlement放在我们这个系统,那么势必需要考虑和产品共用数据库,产品不可能还要通过api来获取这些entitlement,这肯定就影响性能,所以肯定会直接从数据库中读取所有的entitlement。基于这两点考虑,我们并不在本系统中实现entitlement,而是在系统中提供一种嵌入方式,对于产品而言,仅仅需要知道一个用户都属于哪些role,然后利用这些role,去在产品自身的数据库中去找到这些role对应的entitlement,也就是说,在我们的系统中,产品仅细化到role这个层级。对于entitlement这个层级,我们的系统提供一个iframe的嵌入方式,由产品自己写一个页面来进行控制。当然,如果我们不提供这个iframe,其实也是可以的,对于产品而言,一个role对应拥有哪些entitlement,应该都是已经固定的。

entitlement的存储设计

多量的存取问题

entitlement用01表示允许与不允许,用二进制转化为16进制进行存储,当entitlement的数量超过256时,将所有的entitlement按每个单位256个的方式进行分组,每组单独得到一个16进制数,并用英文逗号隔开,将字符串保存到数据库中,再采用redis将结果缓存起来,随时读取,读取时,通过把16进制转换为2进制得到想要的结果。

多选项问题

某一个entitlement可能有多个选项,0代表不可读写,比如1代表可读,2代表可读写。这种情况怎么办呢?

对单记录的权限问题

某一个后面新加进数据库的某条记录,一个用户想要对这个记录进行操作,必须要获得对该记录的对应权限。这种情况怎么办?

API设计

最后,我们要为产品提供api,让产品可以通过这些api来获取某个用户的权利列表。可以说,本系统是一个用户管理系统和权限管理系统的并集。

首先要解决的是用户的注册、登陆、密码设置等问题。目标公司的产品,所有用户都通过这个系统来进行注册、登陆,因此,需要本系统提供完整都Oauth方案。

其次要解决用户信息都修改,也就是说在本系统自己提供都UI界面之外,其他产品,如果有权限,可以通过api进行用户操作,比如修改用户的基本信息,重设用户的role列表等。实际上,系统本身的UI界面,也是通过这些API来操作的,UI界面其实是本系统的一个app实例。

最后就是要能够根据登陆产品的用户,来获取这个用户的权利。如果一个用户登陆了某个产品,那么接下来,产品系统会通过api从本系统获取这个用户在该产品里面拥有哪些权限,获得这些信息之后,用户在产品内的界面现实、操作都会受到影响。

权限分层

对于一个用户的权限,它往往不是由单一的角色信息决定的,而是由角色、许可证、业务逻辑等综合决定的。我这篇文章设计的是站在一个公司拥有多条产品线的层面去思考。但是,当我们回到具体的某个产品的开发团队的角度去思考这个权限系统,就会遇到各种各样的麻烦。

这里提到的“权限分层”的概念,是基于将用户的权限看作“流”的基础上。当要得到一个用户是否对某个操作拥有权限时,要经过层层判断,而非直接从数据库里面取出一个值就够了。代码层面,要通过取出很多值,然后根据“层”的先后顺序,对该用户在每一层上的权限“流”进行拦截和判断,如果不符合条件,直接弹出,用户也就没有该权限,只有符合条件,才会进入下一层进行判断。
一个系统最终会有多少个权限,这取决于有多少个增、删、查操作(借口)。对于一个系统而言,可以说,几乎每一个api都会需要鉴权,如果为了控制界面,还可能需要有一个(堆)接口,帮助客户端判断当前这个用户是否要显示某些东西。而更重要的是api接口,这些接口控制着用户可以看到的东西、可以进行的操作。但是,权限又不能直接反映在具体的某个字段上,它只能以背后“隐藏的手”的形式存在。

将成百上千的接口后面的权限判断,用分层的方式加以总结,会有不错的效果。但是,比较麻烦的是,在撰写每一个接口的时候,都必须要进行层层判断,完成这些判断之后,才能走正常的业务逻辑。有没有一种方式,可以在更高一层去做这些分层的判断,而业务代码就是业务代码,和权限判断分开?有一个概念叫“API网关”,我知道有一个叫apigee的系统专门实现了这种网关,有兴趣可以了解一下。基于这种API网关的概念,我们可以从新考虑API后台开发的架构,即在路由前面再加一层,用来做权限的判断。

小结

这是一个common的用户权限管理系统设计,也就是说它没有考虑每个产品的特殊需要。为了让系统更具有扩展性,在开发这套系统时,应该在某些环节考虑留下hook,以方便日后在某些逻辑过程中新增功能。

当然,本文有一些地方并没有提及,比如对于一个产品而言,能够从本系统获取信息,也需要鉴权,可以通过appkey+secretkey的方式。这些细节其实在业界都是有成熟的解决方案,只需要去实现即可。

2018-03-04 949

为价值买单

本文价值9.49RMB