引言:
权限控制在每个应用中都必不可少,相似却又总不尽相同。有没有一种比较通用的设计甚至框架,可以让我们不用每次都去重复造这个轮子呢?本文主要是向大家介绍下我们的应用基础框架coframe,以及在权限控制方面的一些设计与实践。
目录:
一、应用基础框架简介
二、应用权限模型设计
三、应用权限控制
一、应用基础框架简介
相信很多同学都有过这样的经历,刚做完一个项目,开始一个新项目的时候,发现基础能力又要重新开发一遍,用户,登陆,菜单,机构人员,权限管理等等。重复的工作枯燥而没有价值,却不得不做。能否来个什么框架,可以直接提供这些开箱即用的基础能力?
回答当然是有,我们这里把这种包含了应用基础能力与通用业务模块的框架,称之为应用基础框架。它就像一个半成品的车架,已经有了基本的架子,轮子,发动机,方向盘等,基本上已经可以跑了。但是车子更多的其它能力,还等待人后续去完善。
完整的应用基础框架,应当包括应用后端基础框架与前端展现基础框架(包括 web 或移动)。这样两者配合使用,才能直接为应用提供开箱即用的业务能力。
不论前后端,我们认为框架可以分为以下四层:
基础依赖层:基本上定义的框架的技术栈,采用什么语言,什么框架,依赖哪些基础库或组件等;
基础能力层:定义的是基础能力,后端包括对异常的定义,国际化的处理,ORM模型的抽像等等,前端则是对菜单,路由,通信等的框架化封装,通用基础组件的抽取等;
通用业务层:实现通用的一些业务能力,如用户管理,登入登出,权限控制等等;
用户业务层:这一层交由用户去扩展,实现各自已的业务能力;
Coframe就是我们构建的这样一套应用基础框架。它的后端框架,基于 spring boot + spring security + jpa, 前端基于 vue + iview。
Coframe应用基础框架能力图谱
coframe提供的基础能力有:
组织机构:机构树管理,机构管理,岗位管理,工作组管理,员工管理;
用户认证:用户管理,本地认证,单点登陆(需与 iam 集成);
权限管理:权限模板管理,角色模板管理,角色管理,资源组管理,菜单管理,功能管理;
数据字典:字典类型管理,字典项管理,字典国际化,导入导出;
日志管理:统一日志接口,日志全索。
所有这些能力开箱即用。
下面我们重点分享一下关于应用权限方面我们的一些设计与实践。
二、应用权限模型设计
首先,我们要了解一下,什么是权限。我们认为,权限就是用户对某些资源的控制能力。所以,模型上我们就有了一个抽像的资源概念。
资源按类型具化,可能是用户在门户上可见的菜单,或者是页面上可见可点击的某些按钮,或者用户在应用后端可以调用的某个接口等。
用户通过角色,与这些资源建立了关联,我们才能在系统中通过用户找到这些资源,然后对它进行控制。
因此,用户,角色,资源的关系,就是一个用户可分配多个角色,一个角色,可以关联多个资源。如下图所示:
为了方便角色进行资源授权的管理,我们又提出了几个服务于它们的概念:角色模板,权限模板,资源组。
资源组,顾名思义,就是资源的分组。它是一个树状模型,目前主要设计使用了三层。顶层的代表着某一块的资源总集,比如某个子系统的资源总集。
权限模板,它通过关联多个顶层的资源组,可以代表着某个范围内的可授权资源总集。角色模板关联着权限模板,从它的这个资源集合中挑出了一部分。
角色则是在创建的时候,复制模板的这些资源授权。但它后续可以修改,整个可授权资源仍为权限模板代表的总集。
资源的授权如上图所示。顶层的资源组代表着某一块的资源总集,第二层则代表不同的资源类型,如菜单,功能,环境等。第三层代表着模块,比如机构管理模块的菜单。第三层的资源组之下,才关联具体的资源。对角色或者角色模板进行资源授权配置时,只需要找到对应的资源类型与模块,再找到相应的资源,点击是否勾选即可完成授权,非常地直观。
用户是如何与资源关联起来的呢?如下图,这里我们假设了一个示例。
某应用集成了 coframe,其下又分了三个子系统。为了方便资源的管理,我们为每个子系统设置了一个顶层的资源组,然后为它配置了下层资源组与资源。然后,我们设置了两个权限模板,其中一个负责 coframe 与子系统一的资源,另一个则负责三个子系统的资源。基于两个权限模板,我们分别创建了角色一与角色二。三个用户中,用户一分配了角色一,用户二分配了角色一与二,用户三分配了角色二。用户一可分配 coframe 与子系统一中的资源,用户二可分配所有资源,用户三则只能分配三个子系统的资源。这样,资源的组合与隔离,会变得相当方便与直观。
三、应用权限控制
我们认为,应用的权限控制可以分为如下四类:
1.菜单:用户在应用门户中可见哪些菜单可以通过权限控制;
2.按钮/链接:用户在门户页面中,哪些按钮可见可操作,可通过权限控制;
3.接口调用:不通过门户,直接通过客户端进行接口调用,接口是否允许调用可以通过权限控制;
4.数据:用户是否可见某些数据,能否操作可以通过权限控制。
菜单控制
菜单的控制其实比较简单。用户登陆成功后,前端会再来取一次用户的菜单项。后端服务根据用户分配的角色中,包含的菜单资源,在门户中组织出他所持有的菜单树。
但是菜单的组织过程,却也可以有一些不同。这里我们一种称为动态结构,另一种为静态结构。
动态结构首先要求菜单本身为树状结构,且菜单对应的页面(vue 中叫路由),图标等也记录在菜单中。整个菜单的层次结构,由菜单本身的树状结构表现。这种结构的好处是菜单配置非常灵活,菜单可以完全通过页面操作进行定制。这种结构适合经常需要动态变更的系统,且菜单配置人员对前端要非常地熟悉。
另一种静态菜单结构,则菜单只需要一个平铺关系即可。菜单的层级,顺序,路由,图标等由前端定义,后端只定义用户可以用到哪些菜单。缺点是菜单定制不够灵活,但优点也很明显,非常便于前端开发,前后端分工更明确。
按钮/链接控制
有些系统中,会将按钮的权限与后端功能的权限分开设计为两个模型,但 coframe 中这两个合为一个叫做功能的模型。功能有唯一编码,这个编码在按钮上可控制按钮的显示,在方法上可以控制方法能否调用。
按钮的控制也比较简单,特别是组件化的前端开发框架中。只需要为所有的按钮组件添加一个权限码定义,在页面渲染时,判断一下当前用户是否拥有这个权限码,有则显示此按钮,没有则将按钮从父组件中删除。
接口调用控制
在接口调用控制上,我们考虑过使用现成的一些控制方法,如 spring method security。但是它只能控制到角色级,虽然可以规则匹配,但仍然不够灵活。所以,我们基于功能编码,自行设计了一套。
它的基本原理是,我们使用 annotation,在每个需要控制的方法,添加了调用此方法需要的功能编码。方法被调用时,我们通过 aspect 对方法进行拦截,取得调用它所要求的功能码。然后我们在当前用户所拥有所有的功能码(在用户登陆时已查询出来放在 context 中了)中进行对比,如果存在就允许方法调用,否则抛出无权调用的异常。
鉴于有些需要控制的方法可能在父类中,所以我们定义了两个 annotation,一个定义在类上,可以配置父类上需要控制的方法。另一个定义在方法上,配置方法控制的权限码。查询方法调用所需权限码时,优先在方法上找到,找不到再到类上去查找。
为了方便集成的应用实现接口调用控制,我们定义了一个抽象的方法权限切片处理类,封装了对方法调用的拦截过程。应用只需要继承这个类,配置需要拦截哪些接口即可。
数据权限控制
数据权限,我们认为数据权限 = 数据可见控制 + 数据操作控制。
而数据操作控制,通过按钮控制与接口调用控制组合,基本上可达到想要的效果。
那数据可见如何控制呢?我们归纳为两种:成员法与资源授权法。
成员法,就是用户如果为某个领域的成员,则他就可以看到这个领域,以及这个领域中的资源。如何实现呢?简单一点的办法,就是用户的某个角色,属于这个领域,就可以认为这个用户是这个领域的成员。像租户隔离,devops 中的项目隔离,其实就是用的这种方法。
资源授权法,将某个精确的资源授权角色,或者直接授予用户。成员法实现数据可见控制有一个问题,就是粗度太大,不好实现更精确更细粒度的资源可见控制。要实现更细粒度的,可以使用资源授权法。但粒度细也就代表着管理复杂,孰好孰坏需要权衡后去使用。
精选提问:
问1:请问下数据的可见性控制,是如果做到可配置的?不同的数据,定义的特征和维度都不一样。
答:数据可见性分通过成员授权与资源授权控制。成员授权,比如项目成员,首先是角色有归属,有些角色是只属于这个项目的,如项目经理,开发人员等。为用户分配这些角色,用户即成为了此项目的成员,对项目及项目下的资源可见。资源授权,则是直接将某些特定资源绑定至角色之上,用户配置了此角色,此用户对此资源可见。资源一般用类型与 ID 两个属性标识。
问2:资源授权法就是针对每条数据进行授权吗?
答:资源授权,是直接将某些特定资源绑定至角色之上,用户配置了此角色,此用户对此资源可见。资源一般用类型与 ID 两个属性标识。
问3:请问下生成权限的操作是怎么抽象设计的?比如应用,菜单,页面,api,文件,用户,角色,生成这些资源的权限是如何做的设计?
答:抽像模型上,我们主要为三个:PARTY, AUTH, RESOURCE. party 为参与者,用户,人员,组织,机构,工作组,岗位等都可以归为此类。 AUTH 为资源载体,角色,角色模板可归为此类。资源则包括菜单,功能,应用,api,页面等也都可以归为此类。参与者关联多个资源载体,资源载体关联多个资源,实现资源与参与者的多对多关联。
问4:数据的权限怎么控制,比如微服务中一个A服务,里面又个a资源,里面如何根据数据标识来进行数据权限控制?
答:目前coframe 中的资源,主要通过类型与id (或者编码)来标识。如果维度较多,可以通过添加属性,或者为 id 或编码定制命名规则来扩充。
问5:数据权限里面的继承问题,比如CMDB里面的服务和节点,拥有服务,就拥有了节点的权限,这种该如何控制?
答:这种有继承问题的权限,建议使用成员授权来控制。我们可以为服务配置专用的角色,用户绑定此角色成为服务的管理成员。节点上冗余服务的标识,查询时进行成员关联判断。
问6:数据权限控制里面的,如何让数据对用户不可见,比如a用户,如果没有b资源的权限,就看不到b的权限。
答:原理其实就是在查询数据时,可以根据条件对它进行过滤。成员授权,与资源授权,就是为他们添加关联条件。一个间接,一个更直接。
推荐阅读
普元容器云关键设计和实践之路
容器时代的DevOps部署
有状态容器实践:k8s集成ceph分布式存储
关于作者:秦双春,现任普元云计算架构师。曾在PDM,云计算,数据备份,移动互联相关领域公司工作,十年以上IT工作经验。曾为科企桌面虚拟化产品的核心工程师,爱数容灾备份云柜系统设计师,万达信息的食安管理与追溯平台开发经理。国内IAAS云计算的早期实践者,容器技术专家。