系统权限设计 - 推荐方案

> 我们在做权限设计的时候需要注意的一些点。其中有两点比较关键,这里再次提一下:

  • 粒度:粒度很难把握,推荐以一个基本的“业务操作”为粒度;
  • 区分Access与Validation:其中,Access与数据无关,可以在网关那一层就挡住;Validation与数据有关,可以在下游Service写代码来做。

下面将从后端到前端来介绍整个权限设计的推荐实践细节。

后端实现细节

分别从Access和Validation的实现角度来介绍。

Access怎么做?

Access就是一个个写死的权限。比如在Spring Security中,它是一个个字符串。你可以把它写死在Config文件里,也可以存在数据库里。

一般来讲,存在数据库里更灵活,如果配上一个管理界面,也比较容易管理。这里简单介绍一下存在数据库里如何做。以Spring Security为例,可以使用自定义的Bean拦截所有请求。在Bean里面可以取到request的URL等信息,然后去查数据库或session或解析JWT Token等方式取得当前用户拥有的权限,再去进行匹配。

Validation怎么做?

Validation需要验证数据当前的状态等信息是否满足条件。甚至有时候,不同的角色对同一个状态,也有不一样的权限。Validation在设计和使用的时候,可以考虑以下四个因素:多个角色如何判断权限、短路设计、消除角色、白名单 or 黑名单。下面分别详细介绍这几个因素,然后给出一个推荐的通用Validator代码实现。

多个角色如何判断权限

一般来说,在稍微复杂一点的权限管理需求中,一个人往往有多个角色。那如何判断这个人是否对当前这个操作有权限呢?

按照一般来逻辑来讲,当前用户只要有一个角色对这个操作有权限,我们就认为当前用户对这个操作有权限。

短路设计

因为Validation需要去查询数据。在微服务的环境下,它甚至有时候需要call其它API。前面提到,只要有一个角色对这个操作有权限,我们就可以认为当前这个用户对这个操作有权限。那后续的判断逻辑就可以不走了,程序做成短路设计,有利于减少数据查询和API调用,提升性能。

消除角色

我们在写Validation代码的时候,来自业务方的叙述,可能与角色相关。比如某写作平台,在发布文章后,作者不能再修改文章,但网站的编辑可以。我们用伪代码表示这个validation的逻辑:

这样我们就把“角色”写死到了代码里。假如以后有另一种角色也可以修改文章,比如网络安全审核员。那就需要改代码,重新发布。这样就很不灵活。

我们可以尝试消除代码中的“角色”,而是改成权限。比如,我们赋予editor这种角色一个叫edit_published_article的权限,这样我们的代码就可以写成这样:

这样的话,我们只需要把这个权限赋予给新加的角色,它就可以进行这个操作了。无需修改代码。

那什么时候不能消除角色呢?

但validation一定可以完全消除角色的吗?)不是的。如果你的系统业务,会把角色的id放到业务数据库里,就不能在validation中消除角色

比如我们在上一篇文章中举的例子:如果当前用户是老师,那他可以查看自己课程的试卷。如果是教务主任,可以查看当前年级的所有试卷。这个时候,需要根据不同的角色,去不同的表拿不同的数据。所以“角色”一定会写到validation代码中。这是无法避免的。

但是大多数业务,我们是可以消除角色的。消除角色带来的好处也显而易见,而唯一的缺点是会增加很多权限,使得管理权限变得复杂一些。通常是对应到枚举上,一个枚举的value就会对应一个权限。不过我们可以通过添加“权限组”的概念来解决这个问题,后文会介绍权限组。

通用Validator代码实现

下面给出一个基于Java代码的通用Validator实现及其用法。读者也可以根据自己的需要进行增强:

前端实现细节

处于对系统安全性的要求,我们在后端是必须要做权限控制的。而前端有时候也需要做相应的权限控制,是希望能在UI上给用户更好的体验。比如,不该当前用户看到的页面,就不会出现在左边的导航栏。用户不能点击的按钮,就应该隐藏或者置灰。

页面权限控制

页面显示通常是比较粗粒度的UI控制了。如果角色及其权限相对稳定,可以死在前端配置里,这样开发成本比较低。

而如果角色及其权限容易变化,可以后端返回路由配置,这样就实现了用户,角色,路由的动态配置,全部统一管理。

组件权限控制

组件权限控制是一种比较细粒度的UI控制。具体来讲,有两个方案:

  • 前端写验证逻辑;
  • 所有逻辑都在后端,后端返回Flag,前端根据这个Flag判断。

这两种方案各有优劣,下面我们来讨论一下。

前端写验证逻辑

如果是前端写验证逻辑,就是前端通过已有的数据,去判断组件是否可以显示或者可以操作。比如很多时候,某个按钮可不可以点击,是根据用户的角色,或者当前数据的状态来判断的。在一个表格页面,用户的角色和当前数据都是已知的。所以前端只需要写一个与后端一模一样的逻辑,就可以控制了。

这就会带来一个问题。比如我们删除一个数据,会根据这个数据的状态来做验权。后端肯定是需要写这个验证逻辑的,如果前端再写一份,那就会在前后端各自维护一段相同功能的逻辑。后期如果要修改逻辑的话,就需要前后端同时修改,造成代码维护上的不便。

另外一个问题是,如果前后端理解不一致,可能就会造成前端按钮看起来可以点击,但点击后,后端报了403错误。这可能是由于程序BUG,但如果前后端分离开来,就加大了在开发过程中,这种BUG产生的几率,降低开发效率。

还有一种情况是不适用于在前端写验证逻辑的。就是有些比较复杂的Validation,需要查其它数据库甚至是其它服务的数据,这种情况就不适合在前端做,不然可能要多Call好几个API。

后端返回Flag

如果是后端返回Flag,就可以解决上面提到的两个问题。这个时候,验证逻辑全部放到了后端,后端在“读”数据的时候,和真正进行业务操作“写”数据的时候,可以复用同一个Validation的逻辑。

后端返回Flag就是完美的解决方案吗?不是的。它同样会有两个问题。

第一个是对response结构的侵入。我们会在response里面加一个甚至是多个Flag,而这些Flag其实是跟业务数据是无关的。这里比较建议的是用偏业务的叫法来命名Flag,而不是偏前端UI的叫法。比如,叫canDeleteXXX比叫showXXXButton要好。

另一个问题是,有些操作可能只需要Access控制,不需要Validation。这个时候,其实后端也没有复用任何代码,因为进行“写”操作的时候,会在网关那一层通过Access验证权限,进来了没有走任何Validation。所以这种情况下,单纯为了加Flag,在读数据的时候去写逻辑判断Flag,反而不好。

推荐方案

对于一个操作的权限控制,通常有两种情况:

  • 只需要Access,
  • 需要Access + Validation

综上两种实现的比较,笔者推荐的方案是:后端返回的Flag只与Validation有关,前端写死的代码里只与Access有关。

下面是以Vue为例的一个示例代码:

当然,不同团队可以根据自己的实际情况进行取舍和改进。

用户组与权限组

有时候我们可能会根据业务需求,对RBAC模型进行一定的增强。比如用户组、权限组等。

用户组

如果用户太多,对一个一个用户管理角色可能会比较困难。这个时候我们可以抽象出“用户组”的概念。相当于公司的“部门”。这样就可以对一组用户来管理角色,可以让管理更加方便。

权限组

在前面我们提到,有时候在Validation中,可以“消除角色”。这带来的代价就是会根据数据的状态创建不同的权限,使得权限增多。比如高中有3个年级,我们想分别对这三个年级有不同的权限控制,就得创建三个权限。

另一种情况是Access对API的关系。在上篇文章中,笔者推荐的是以“业务操作”为粒度。比如发朋友圈,假设有三个步骤:上传图片,获取当前位置,确认发布。我们其实只需要一个发朋友圈的Access,而不是三个Access。但这个Access其实对应的是三个API,而每个API又可能不止一个Access。比如上传文件,我们在聊天的时候也会用到这个API。所以Access与API是多对多的关系。

权限多了,就不容易管理。所以可以抽象出一个权限组的概念,来更好地管理权限。

当然了,增加用户组和权限组都会带来一定的复杂性,使现有的权限模型变得更加复杂。所以再次提醒大家,在做权限设计的时候一定要遵循“够用就行”的原则,切勿过度设计

以上两篇文章是笔者对权限系统设计的理解和总结。如果读者有任何疑惑的地方,或理解不一致的地方。欢迎留言讨论~

网络安全成长路线图

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:

# 网络安全学习方法

​ 上面介绍了技术分类和学习路线,这里来谈一下学习方法:
​ ## 视频学习

​ 无论你是去B站或者是油管上面都有很多网络安全的相关视频可以学习,当然如果你还不知道选择那套学习,我这里也整理了一套和上述成长路线图挂钩的视频教程,完整版的视频已经上传至CSDN官方,朋友们如果需要可以点击这个链接免费领取。网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

针对 OA 系统的特点,权限说明: 权限系统中,权限通过模块 +动作来产生,模块就是整个系统中的一个子模块,可能对应一个 菜单,动作也就是整个模块中(在B/S 系统中也就是一个页面的所有操作,比如“ 浏览、添 加、修改、删除” 等)。将模块与之组合可以产生此模块下的所有权限权限组 为了更方便的权限的管理,另将一个模块下的所有权限组合一起,组成一个“ 权限组” ,也就 是一个模块管理权限,包括所有基本权限操作。比如一个权限组(用户管理),包括用户的 浏览、添加、删除、修改、审核等操作权限,一个权限组也是一个权限。 角色 权限的集合,角色与角色之间属于平级关系,可以将基本权限权限组添加到一个角色中, 用于方便权限的分配。 用户组 将某一类型的人、具有相同特征人组合一起的集合体。通过对组授予权限(角色),快速使 一类人具有相同的权限,来简化对用户授予权限的繁琐性、耗时性。用户组的划分,可以按 职位、项目或其它来实现。用户可以属于某一个组或多个组。 通过给某个人赋予权限,有4 种方式( 参考飞思办公系统) A . 通过职位 a) 在职位中,职位成员的权限继承当前所在职位的权限,对于下级职位拥有的权限不可继 承。 b) 实例中:如前台这个职位,对于考勤查询有权限,则可以通过对前台这个职位设置考勤 查询的浏览权,使他们有使用这个对象的权限,然后再设置个,考勤查询权(当然也可以不 设置,默认能进此模块的就能查询),则所有前台人员都拥有考勤查询的权利。 B. 通过项目 a) 在项目中,项目成员的权限来自于所在项目的权限,他们同样不能继承下级项目的权限, 而对于项目组长,他对项目有全权,对下级项目也一样。 b) 实例中:在项目中,项目成员可以对项目中上传文档,查看本项目的文档,可以通过对项 目设置一个对于本项目的浏览权来实现进口,这样每个成员能访问这个项目了,再加上项目 文档的上传权和查看文档权即可。 c) 对于组长,因为可以赋予组长一个组长权(组长权是个特殊的权限,它包含其他各种权 限的一个权限包),所有组长对于本项目有全权,则项目组长可以对于项目文档查看,审批, 删除,恢复等,这些权限对于本项目的下级项目依然有效。 C. 通过角色 a) 角色中的成员继承角色的权限,角色与角色没有上下级关系,他们是平行的。通过角色 赋予权限,是指没办法按职位或项目的分类来赋予权限的另一种方式,如:系统管理员,资 料备份员… b) 实例中:对于本系统中,全体人员应该默认都有的模块,如我的邮件,我的文档,我的 日志,我的考勤……,这些模块系统成员都应该有的,我们建立一个角色为系统默认角色, 把所有默认访问的模块的浏览权加入到里面去,则系统成员都能访问这些模块。 D. 直接指定 a) 直接指定是通过对某个人具体指定一项权限,使其有使用这个权限的能力。直接指定是 角色指定的一个简化版,为了是在建立像某个项目的组长这种角色时,省略创建角色这一个 步骤,使角色不至于过多。 b) 实例中:指定某个项目的组长,把组长权指定给某个人。 针对职位、项目组: 如果用添加新员工,员工调换职位、项目组,满足了员工会自动继承所在职位、项目组的权 限,不需要重新分配权限的功能。 用户管理 用户可以属于某一个或多个用户组,可以通过对用户组授权,来对组中的所有用户进行权限 的授予。一个用户可以属于多个项目组,或担任多个职位。 授权管理 将一个基本权限或角色授予用户或用户组,使用户或用户组拥有授予权限的字符串,如果角 色、职位、项目中存在相同的基本权限,则取其中的一 个;如脱离角色、职位、项目组, 只是取消用户或用户组的中此角色、职位、项目组所授予的权限。用户所拥有的权限是所有 途径授予权限的集合。管理员用户可以 查看每个用户的最终权限列表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值