一般来说,权限有许多种,我们经常用到的一般有操作权限和数据权限两种。

所谓操作权限就是有或者没有做某种操作的权限,具体表现形式就是你看不到某个菜单或按钮,当然也有的是把菜单或按钮灰掉的形式。实际上它的实现机制比表面上看到的要复杂得多,比如:我们从浏览器访问过一个地址之后,实际上这个URL就会在历史中存在,这时就会存在一种可能,有的人虽然没有权限,但是他知道怎么访问的URL,如果他再有一定的技术基础,那么通过猜测,有时候就可以得到真正的操作的URL,这个时候如果操作权限限制做得不到位,那么他就可能做他非授权的事项,所以操作权限一般都在显示界面做一次控制,过滤没有权限的操作菜单或按钮,另外在真正执行操作时,还要进行一次权限检查,确保控制非授权访问。

所谓数据权限,就是有或者没有对某些数据的访问权限,具体表现形式就是当某用户有操作权限的时候,但不代表其对所有的数据都有查看或者管理权限。数据权限有两种表现形式:一种是行权限、另外一种是列权限。所谓行权限,就是限制用户对某些行的访问权限,比如:只能对本人、本部门、本组织的数据进行访问;也可以是根据数据的范围进行限制,比如:合同额大小来限制用户对数据的访问。所谓列权限,就是限制用户对某些列的访问权限,比如:某些内容的摘要可以被查阅,但是详细内容就只有VIP用户才能查看。通过数据权限,可以从物理层级限制用户对数据的行或列进行获取,这种方式比把所有数据拿到之后再根据用户权限来限制某些行或列有诸多好处:

  • 性能提升:获取的数据量较少,节省了网络流量和数据库IO,一定程度上可以提升性能
  • 翻页处理更为准确:如果通过后续逻辑去掉某些行,就会导致一页显示的记录条数不足,或者采用这条记录只显示部分内容,但是操作权限方面做限制的方式,这个时候就产生一个悖论,你不想让我操作,为什么让我看到?你让我看到为什么不让我操作?
  • 安全风险提升:理论上让攻击者看到的信息越多,被攻击的风险也越大。通过后续业务逻辑限制的方式通过攻击控制层或展现层的代码,理论上就可以获取到所有的数据。
  • 代码编写工作量增加:由于数据是正常全部返回的,只是通过后期的逻辑进行了控制,这个时候就需要开发人员增加相关的代码来进行判断和控制,这个时候会把高层级的限制逻辑暴露给底层业务开发人员,并由他们来编写代码进行限制。
  • 代码重构工作量增加:当权限限制逻辑或范围有变化的时候,程序员们就需要对这部分逻辑进行重构,稍不注意就会出现数据安全问题。

操作权限相对来说比较简单,目前也有比较好的解决方案,因此今天的不讨论操作权限,今天重点来讨论数据权限的实现方案。

需求整理

一个好的数据权限方案要考虑哪些问题呢?我觉得要考虑以下方面:

  • 对程序员透明:整个方案对于程序员们来说,不论是前台还是后台的程序员们来说,越透明越好,他们最好不知道有这么回事情
  • 与不同的用户权限能较好集成:作为一个数据权限系统来说,肯定要和用户、角色、组织机构什么的数据打交道。如果这个方案只能和某一种用户权限系统集成,那么它充其量只能算是一个可用的解决方案,但是注定算不是上好的解决方案。
  • 性能损失最少:我们说,要增加新的功能或限制的过程中,肯定会对性能方面有一定影响的,怎么样能把性能损失降低,甚至能提升一定的性能是设计师们永恒的主题。
  • 对于分页能良好支持:上面有说到当采用了行权限时,有些行是不能显示给用户的,这个时候不能对分页有影响,比如本来是一页是10条的,现在忽然变成8条,甚至在极端情况下变成0条,这样的用户体验就非常差了。
  • 一次设定到处生效,哪怕是不同的ORMapping方案也都起作用:我们知道,现在采用唯一的一种ORMapping解决方案的场景已经越来越少了,不同的方案都有自己的优缺点,它可能只适应某种前置条件下的应用场景,实际应用的情况下根据场景不同使用不同的解决方案也是非常普遍的,这就要求解决方案有一定的普适定。
  • 跨数据库:一个项目,它的运行环境基本上是确定的。但是对于一个产品来说,根据用户的实际场景不同,可能对于数据库也有不同的要求,这个时候就需要对不同的数据库有支持,不能对于A数据库是支持的,对于B数据库的场景下,就出异常或者结果不正确了。

需求分析

由于要对程序员透明,因此解决方案不应该在DAO层实现。

由于要和不同的用户权限系统进行集成,因此确定了不能在DAO层实现。

对于分页有良好的支持,决定了不能在DAO层和显示层实现。

一次设计到处生效,对多种ORMapping方案生效,决定了不能在ORMapping层实现。

要求能跨数据库,进一步提升了方案的通用性和普适性,也提升了实现的难度。

数据行权限不是所有的过滤条件都是和用户、角色、岗位字段有关的,不是所有的表中,都有用户、角色、岗位字段,这个时候也要能良好支持。比如:人员信息表中:不同的级别的HR,可以看到不同级别的人员信息。总之过滤条件可能是数据表中的任意字段。

数据列权限是控制列的可见性的,也就是说,同样对这行数据有权限,但是可能对其中的几列没有权限,则这几列不可见。

比如表table1 有(a,b,c,d,e,f)字段,列权限控制可能控制c,d不可见。但是程序员写代码的时候可能写的是select * from table1,这种情况下也不能返回c,d字段。

实现思路

呵呵,估计现在已经把许多方案已经逼到垃圾堆了。说实际关于这个主题,我也思考了相当长的时间,一直没有好的解决办法。因为我自己给出了N个解决方案,又都被自己所推翻,因为这些方案距离我期望的方案都有相当的距离。

直到有一天,开一个无聊的会,然后就走神了,忽然之间冒出一个想法,然后仔细品味一下,貌似可以讲通,于是和开发人员进行了一下推演,技术实现上没有什么问题, 那就先做一版出来再说。

下面简单说一下思路,真正的实现思路将在真正实现之后再行补充。

归根到底,数据权限的实现思路就是个调整SQL的问题,直白点就是做加减法的问题。

对于行权限来说无非是把一部分SQL条件附加到程序员写的SQL语句中,对于列权限来说则是把程序员写的SQL语句一部分从SQL里减去的问题。

然后问题就转化为在哪一层里对程序员的SQL进行处理的问题,由于上面在提需求的时候附加了一系列的条件,因此也就确定了这一层不可能在DAO层,也不可能在ORMapping层来解决,这个时候可以添加层次就和分库分表方案一样了,只能在JDBC层或者协议代理层来解决,相对来说协议代理层离得太远了,因此在JDBC层就是一个合适的选项。

上面一段说离得太远,是指在应用中,这些 SQL的加减需要根据当前登录人员的身份进行判定,在代理层要获得当前登录人员的信息会非常复杂,因此会增加额外的复杂度,确实是离得太远。但是在JDBC层做起来就方便多了。

在JDBC层来进行处理,有几个好处,第一:所有的SQL语言,不论用得什么ORMapping方案,最后总是绕不过这一层的;第二、这一层实现的话,对于开发人员来说是全透明的;第三、这一次离业务实现足够近,可以比较方便的获得当前登录用户的相关信息。

故事讲到这里,整个实现方案已经没有什么悬念了:通过在JDBC层对要处理的SQL语言进行解析,然后根据定义好的数据权限规则对SQL进行处理,通过增减SQL片断,使之达成数据权限控制的目的。

总结

通过上面对问题的分析,需求的整理,需求的分析及设计思路的思考,我就对数据权限的来龙去脉及其解决方案进行了比较深入的分析,亲们可以根据我的思路自行实现,也可以期待我们实现。

注意:本方案只针对JavaEE场景,对于非JavaEE不能保持方案的可实现性。