背景
最近一直在思考,关于工作、生活、人生的等等,都说生活应该有诗和远方,可是眼前的苟且总是让人身心疲惫。我们是应该随波逐流,还是坚持自我,一直想找个理论来支持坚持自我的理由。有时候想一想,那种“举世皆浊我独清”的感慨是何等的气魄!听说论语不错,打算接下来的一段时间重拾小学课本,温故而知新。言归正传,今天想给大家分享下,如何做数据权限的授权,一个好的数据权限授权系统应该如何设计?
大道至简
软件架构,就是解决软件复用性、可扩展性问题。尽管说好的系统架构不是设计出来的,是不断演进出来的,但是如果前期没有良好的基础架构,后续的软件版本迭代会困难重重。做任何一件事情,我们都得清楚的知道:应该怎么做,而不是苟且于当下。活在当下,更要展望未来。最近一直在做数据中台相关平台的工作,包括规划、架构设计、落地开发等工作。随着角色的切换,慢慢了也具备了一些不同层面的思维,比如:产品思维、架构思维、管理思维。这期间发现有些东西已经脱离于具体的做事,然而对于自己来说心境的提升非常重要:没有人能让你生气,除了你自己。多一些真诚,少一些套路。没有套路就是最好的套路!
数据授权
一直有个梦想,做一套通用的用户账户系统,其不是大而全,而是模块化可插拔。通用和个性化,一定会有个边界,一个临界点,这个边界因系统而异,因人而异。业务上的要求很简单,可以对用户或者角色,授予不同的资源访问权限。首先我们明确一些概念,这些是沟通交流的基础:
1、授权的资源主体
在中台系统中,我们认为授权的主体是表。授权的粒度可以是:表字段、表、主题、主题域、项目等。就比如设备管理平台中,产品和设备的关系,把产品当成设备的附属,维度会更加好一些,这样能明确管理的主体是谁,也方便进行业务设计、微服务拆分等。
2、授权的资源权限
读(Read)、写(Write)、改(Modify)、删(Delete),这些就是对资源能操作的动作。授权最终是实现数据分享,一般针对读写权限进行操作。
3、权限树
对一个资源的授权过程,我们分为两个环节。第一是授权的资源范围,第二是操作的编码。对于资源范围的限定,我们借助于treepath的概念,首先构造资源树,每个树节点都有自己的path编码,最终会形成层级的资源编码,形如:
P{xyzn}-T{xyzn}-T{xyzn}-M{xyzn}-F{xyzn}。其中:
项目path编码:P{xynn}
主题域、主题path编码:T{xynn}
表path编码:M{xynn}
字段path编码:F{xynn}
4、授权表
我们以授权的主体来构造授权表,区别于基于用户。大体的表结构如下:
CREATE TABLE `sys_res_grant` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`res_type` char(1) NOT NULL COMMENT '资源类型',
`res_id` bigint(20) NOT NULL COMMENT '资源ID',
`res_path` varchar(300) NOT NULL COMMENT '资源PATH',
`res_dept_id` bigint(20) NOT NULL COMMENT '资源所属部门ID',
`target_type` char(1) NOT NULL COMMENT '授权目标类型',
`target_id` bigint(20) NOT NULL COMMENT '授权目标ID',
`privileges` varchar(500) NOT NULL COMMENT '权限信息,逗号分隔',
`operate_by` bigint(20) NOT NULL COMMENT '操作者',
`operate_by_name` varchar(120) NOT NULL COMMENT '操作者名称',
`operate_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=200 DEFAULT CHARSET=utf8 COMMENT='资源授权表';
我们将授权的表字段、表、主题域、主题、项目等都抽象为资源。利用资源path来方便授权范围的检索。举个简单的例子,现有资源M1,其资源path为P0001-T0001-T00111-M0001。将此资源,授权给P0002项目下的R0角色则会有如下配置:
目标所属项目ID:P0002,角色:R0,资源Id:M1,资源类型:表,授权资源所属空间ID(项目):P0001,权限编码:P0001-T0001-T00111-M0001
在进行权限校验的时候,会涉及到两个层面的事情,比较抽象,需要进行深入的理解。我们从业务场景上来说,给U1用户授权表M1访问权限、M2访问权限。当下我们是基于每张资源表来做授权操作的,那么要实现这样的功能,就需要授权两次的,分别针对表M1和M2。可以知道每次针对表的授权,其实都是追加的方式。如果换种角度会更加容易理解其底层逻辑,如果针对用户、角色进行资源授权,只需要授权一次,选择M1、M2两个资源即可。现在无非就是将操作的主体由用户角色换为资源表,本质的实现逻辑应该是一致的。接着我们更进一步的进行分析,对于同级的数据授权,我们清楚有几个是集合并的关系。那么对于层级间的授权关系呢?来看下具体的授权例子:
操作一:给U1用户授权访问T00111主题下的所有表资源,则授权表中会存在一条记录,其对应的资源path应该为:P0001-T0001-T00111
操作二:给U1用户授权访问T00111主题下的M1、M2表资源,则授权表中会存在两条记录,其对应的资源path分别为:P0001-T0001-T00111-M0001、P0001-T0001-T00111-M0002。
很显然的,我们希望用户权限是操作一和操作二所赋予权限的交集,其实就是操作二赋予的权限。就有如下的结论:
对于同级别的授权记录信息,需要取并集,而对于层级授权记录信息,需要取交集。
SQL实现
我们来看下在MySQL数据上,如何写一段SQL来实现数据权限的功能。请看下面一段SQL:
with 授权表(...)
select resPath,* from 资源表
--获取资源path(P-T-T-M)
left join xxx on ...
where
是我所创建的(读写、修改、删除)OR
授权判断(
--如果存在P-T-T-M粒度授权则进行判断,否则忽略
P-T-T-M存在授权 (exists select 1 from 授权表 where userid=... and dept_id=... and resPaht like grantPath%) AND
--如果存在P-T-T粒度授权则进行判断,否则忽略
P-T-T存在授权(...) ADN
--如果存在P-T粒度授权则进行判断,否则忽略
P-T存在授权(...) AND
--如果存在P粒度授权则进行判断,否则忽略
P存在授权(...)
)
逻辑还是比较清晰的,对于授权判断部分的SQL条件拼接,只能采用预处理的方式来实现。我们可以先按当前用户角色查询出其所有的授权记录信息,然后对其进行归类,并拼接条件SQL片段。
结尾
人不知而不愠,不亦君子乎。