改造电商交易后台权限管理过程

在Qunar做项目时,曾经接手一个特别大的需求:改造我们交易后台系统的权限管理。

背景

当时那个后台系统的权限管理真的是一团乱麻,具体包括:

  • 系统里默认指定了几种角色,每个用户属性上只能配置一个角色属性,代码里每个角色能做什么事情是写死的(很多系统前期都会这么干)
  • 曾经做过许多活动,几乎每个活动的管理员是单独写死的,并散落各处(硬编码我们都会)
  • 使用这个后台的除了Qunar自己的管理员,还有供应商的管理员。最开始供应商管理员的权限是相同的,后期供应商管理员也要支持自己的超级管理员和普通管理员,还能在自己的店铺下对自己的人做权限分配
  • 项目最开始是使用servlet写的,后来改使用springmvc,但是servlet的许多接口依旧被使用着,导致接口命名有多种情况,提供对外接口的类也是各处都有
  • 接口的命名也是各种各样,代码里无法通过接口名称区分哪些是后台接口,哪些是前台接口

设计及开发过程

经过一番仔细的调研后,我果(bei)断(po)选择了重新写一套权限系统。说一下为什么没使用流行的spring security和apache shiro框架呢,因为之前后台的用户是这样登录的:首先走qunar网站的登录流程登录成功,这时会在指定域下种下cookie,然后后台通过指定的工具包解析cookie获取用户信息,这个是不能变的,与那两个框架自己管理认证机制及授权流程不一致。

接下来是具体设计和开发了,怎样一步步才能把这堆乱麻梳理好呢

  • 首先要有一套独立的完整的基于RBAC(Role-Bases Access Control)模型的管理功能。这里呢,我们的管理员和后台模块也特别多,因此要对RBAC模型进行扩展:在用户上层要引入部门,在权限点上层要引入一层权限模块,这样可以树形的查看用户和权限点,便于使用
  • 能通过角色进行分配权限和用户之后,要重点考虑如何对原来的“权限”进行兼容了。首先是对系统里原有的几种角色属性的兼容,新增对应的角色,分配对应的权限,把之前配置该角色的用户都绑定到RBAC的角色里来,这样这个用户在角色属性一层完成了权限的迁移;接下来是对活动权限的兼容,为每一个活动新建一个管理员,把之前负责活动的用户绑定到对应的角色上,然后为这些角色配置管理对应活动的权限就可以了。
  • 有了管理模型,也能对原来数据进行兼容,下一步就是确定要拦截的接口了。因为接口比较乱,如何确定要拦截哪些接口呢,在人工挨个类检查不现实的情况下,写个脚本丢到后台系统的服务器上,记录后台系统被访问的url列表,跑几天就可以拿到大部分的接口了,个别漏网的的,基本也不是核心的了。
  • 接下来就是实现权限拦截了,拿到当前用户和访问的url,根据RBAC模型管理的数据,判断是否有权限访问。这里有一个比较复杂的情况,就是早期serlvet接口许多是通过参数的不同定义不同的界面,比如:出售中产品页面请求是/product.do?type=onsale, 下架产品页面请求是/product.do?type=offline,为此,我单独在权限里支持了对参数的检测,检测到配置的权限里包含参数时,逐个参数读取查询匹配,来找出最终该校验的权限点,间接的实现了对数据权限的处理。
  • 接口的权限拦截做完了,考虑到我们后端页面有一些是jsp页面做的,jsp的页面上许多按钮可以在渲染时判断是否有权限,有权限才显示,因此又开发了一套做权限校验的标签。用标签将需要判断权限的按钮包起来,传入对应的权限唯一标识码,页面加载时无权限的按钮就不会显示。后期做前后端分离时,页面这些按钮是否展示改为先调接口判断了,默认不展示,接口返回有权限再进行展示。
  • 权限拦截到此并没有完,因为每次校验都会涉及到多张表的查询操作,这会对数据库增加特别多的访问,而用户的权限本身变化频率会很低,因此权限拦截的许多点要上缓存

实施过程

这些都做好后,如何保证安全上线呢?当时分步骤的发布了好多次

  • 第一次,上线RBAC模型的所有管理功能,然后配置好需要的角色,只将一个小功能切到使用新的权限框架拦截管理
  • 第二次,使用新的权限框架管理Qunar管理员端的所有后台功能,这次配置的工作量较大,几乎要把后台的url都配置一遍,还要做好分配
  • 第三次,使用新的权限框架管理供应商端的管理功能了,这次风险比较大,选择在凌晨大家都睡觉的时间发布。当时考虑到许多供应商可能不懂如何再提供的页面做自己店铺下的权限分配,因此运维侧准备了教学ppt,开发这边我特意增加了功能,能以某个管理员的超级管理员身份进入店铺协助维护。
  • 第四次,上线一个特别的功能:根据登录用户已分配的权限,生成后台的多级菜单。这个的前提是权限点类型区分了菜单和按钮,同时要求每个模块下面只有一个菜单,保证这个权限模块和权限点的管理就是对实际菜单项及菜单对应页面下的管理,这样根据用户分配的菜单权限,计算出由权限模块和权限点组成的树形结构,然后前端在jsp页面使用JSTL渲染就可以了。
    至此,整个权限切换就告一段落了。

后续

后面根据项目陆续的还上了一些实用的工具,这些工具对绝大部分的权限系统都适用,包括:查询指定用户已分配的角色、查询指定用户的当前拥有的权限、查询指定权限被哪些角色拥有、查询指定权限被哪些用户拥有等,这些功能可以很大程度的方便权限管理员对权限数据进行维护,另外还支持了动态刷新缓存中的用户权限,在修改了权限配置后让用户权限实时生效。有一个功能没上但非常值得一提的,就是恢复管理员的某次操作,这个在有人恶意操作或错误操作后能及时的回滚权限数据。

当然了,这套权限系统可以支持许多扩展,比如:部门里增加主管的概念,主管拥有下面员工权限的总和;不光可以对用户分配权限,还可以对部门分配权限,部门下用户都拥有部门分配的权限;部门层级关系有实际含义时,权限跟着继承等等。这些都可以随着业务的复杂进行扩展,只是暂时还不需要。

除此呢,这套权限系统也有不想管的一面,最重要的就是横向越权。横向越权本身和业务联系特别紧密,比如一个供应商管理员要操作一个产品,这时想校验一下这个产品是否属于这个供应商下面的,那么就要去获取产品id,这时你可能就遇到,产品的id的参数可能是productId,productID,id,pId等等中的一种(不同的开发人员定义的参数名称不同),好不容易拿到这个参数了,有时你还会发现有时这个值是产品的id,有时又是加密后的值。你好不容易把这个也搞定了,这时业务变化了,出现了新的业务形态:一个大供应商下面有多个小供应商,然后他们的产品可以共享… 说这些呢,主要是想说明一点,横向越权最好交给业务层去做,放在权限可以做,但可能会和某些业务排斥,同时呢,如果在权限管理里做,那么需要后续的接口都要遵照某些规范才可以。

结尾

权限系统改造过程中,我输出了大量的wiki来介绍改造后的权限框架,但组内成员依旧很难独立完成权限的配置,因此我又前后做了四次关于新权限管理的分享,覆盖了涉及到的技术、算法及使用时的细节。最后获得一个称号:权限帝。

(完)

点赞