前端数据级权限到底怎么玩

定义

RBAC权限体系大家都很清楚了,一般平时的后台管理系统都会用到,github上也有大把的权限模版项目,但都基本只支撑到角色这个颗粒度(这里指的是前端实现上)。那么在某些场景下就不会太适用,比如一个集团公司,有10个子公司,总部的会计要对子公司的财务数据进行工作,假设一个人负责一个子公司的数据,需要10个角色就可以完成分配。看起来很理想,但实际上情况要比这复杂的多。

  1. 小A会计离职,小B则需要负责两个子公司的数据
  2. 会计组来了卷王他需要负责5个子公司的数据
  3. 会计部长需要看到全部子公司的数据
  4. 子公司的增减
  5. ...

看到这里大家会说不就是多建几个角色的事情吗,但从数学的角度来讲n个公司会有多少这种角色呢,答案是2^n个,考虑到空集无实际意义,也就是2^n-1个角色的可能,这就容易造成前端角色爆炸,与角色绑定一起的相关判断也会变得很难维护。有人把数据级权限认为就是一个table后端返回不同的数据就完事,但实际上应该要控制到页面的资源级别。

作者认为颗粒度应该到某个图片/字段的可见性控制、表单提交的校验逻辑、甚至一些交互的顺序都应根据权限配置来进行控制

基本概念

作者给出的方案是在权限下面新增一个岗位职能的概念。来个例子:大家都是会计的角色,分配的菜单与按钮都一致,但操作的数据完全不一样,换句话说就是角色负责菜单、按钮,而岗位职能负责具体能看到并能操作的数据来源与字段。(下文岗位职能统称为岗位)

话不多说直接上数据结构

json 复制代码
[
    {
      "httpUrl": "",
      "icon": "ep:aim",
      "levelId": 1,
      "menuId": 2713021,
      "menuName": "菜单-46",
      "menuParentId": 0,
      "systemCode": "A-SYS",
      "children": [
        {
          "httpUrl": "",
          "icon": "",
          "levelId": 2,
          "menuId": 2713022,
          "menuName": "菜单-47",
          "menuParentId": 2713021,
          "systemCode": "A-SYS",
          "children": [
            {
              "httpUrl": "",
              "icon": "",
              "levelId": 3,
              "menuId": 2713025,
              "menuName": "菜单-48",
              "menuParentId": 2713022,
              "systemCode": "A-SYS",
              "pressButtons": [
                {
                  "Name": "新增",
                  "code": "BTN-NEW"
                },
                {
                  "Name": "编辑",
                  "code": "BTN-EDIT"
                }
              ],
              "permissions": [
                {
                  "jobId": "810000197205203382",
                  "jobName": "岗位职能-A"
                },
                {
                  "jobId": "630000201612261761",
                  "jobName": "岗位职能-B"
                }
              ]
            },
            {
              "httpUrl": "",
              "icon": "",
              "levelId": 3,
              "menuId": 2803012,
              "menuName": "菜单-49",
              "menuParentId": 2713022,
              "systemCode": "A-SYS",
              "pressButtons": [
                {
                  "Name": "编辑",
                  "code": "BTN-EDIT"
                }
              ],
              "permissions": [
                {
                  "jobId": "360000199110139912",
                  "jobName": "岗位职能-C"
                },
                {
                  "jobId": "45000019960318289X",
                  "jobName": "岗位职能-D"
                }
              ]
            },
            {
              "httpUrl": "",
              "icon": "",
              "levelId": 3,
              "menuId": 2713026,
              "menuName": "菜单-50",
              "menuParentId": 2713022,
              "systemCode": "A-SYS",
              "pressButtons": [
                {
                  "Name": "导出",
                  "code": "BTN-EXPORT"
                }
              ],
              "permissions": [
                {
                  "jobId": "330000197309154429",
                  "jobName": "岗位职能-E"
                },
                {
                  "jobId": "540000201904112854",
                  "jobName": "岗位职能-F"
                }
              ]
            }
          ]
        },
        {
          "httpUrl": "",
          "icon": "fa fa-calculator",
          "levelId": 2,
          "menuId": 2713023,
          "menuName": "菜单-54",
          "menuParentId": 2713021,
          "systemCode": "A-SYS",
          "children": [
            {
              "httpUrl": "",
              "hidden": true,
              "icon": "",
              "levelId": 3,
              "menuId": 2773015,
              "menuName": "菜单-55",
              "menuParentId": 2713023,
              "systemCode": "A-SYS"
            }
          ]
        }
      ]
    },
    {
      "httpUrl": "",
      "icon": "",
      "levelId": 1,
      "menuId": 1110026,
      "menuName": "菜单-59",
      "menuParentId": 0,
      "systemCode": "A-SYS",
      "children": [
        {
          "httpUrl": "",
          "icon": "",
          "levelId": 2,
          "menuId": 1110027,
          "menuName": "菜单-60",
          "menuParentId": 1110026,
          "systemCode": "A-SYS",
          "pressButtons": [
            {
              "Name": "编辑",
              "code": "BTN-EDIT"
            }
          ],
          "permissions": [
            {
              "jobId": "410000198705127101",
              "jobName": "岗位职能-C"
            },
            {
              "jobId": "210000197008073054",
              "jobName": "岗位职能-D"
            }
          ]
        }
      ]
    }
  ]

可以看到岗位信息作为一个数组反映了当前人员当前菜单所能操作的页面内容权限的集合。那么就以这个森林形式的树形数据结构作为数据级权限控制的配置依据

这里需要注意的是按钮是挂载到角色上面,因为作者在实际工作中发现按钮一般作为功能的发起点,通常上线之后就会固定好使用人群的范围,变动的概率不高。对于页面中其他资源的控制,变动频繁的可以放在岗位下面,反之可以放角色下面

permissions字段反映了当前人员在当前页面所能看到的页面资源与操作数据的范围;假设页面进入时默认选择了"岗位职能-C",那么可以看到"岗位职能-C"控制下的页面资源、后端根据此岗位id所返回的数据以及前端开发根据此岗位ID进行的特殊逻辑处理。至于怎么切换到"岗位职能-D",页面加一个select的选择框就行,并不会造成产品上的冗余,反而有利于提高用户的使用感知度,例如起一个相关的名字"A自公司的账单审查"。因为产品也大概率会加一个字段用于区分不同的数据来源,而现在只需要切换到相应的岗位就能确定使用人员看到与操作的数据权限范围

总结

我们总结出前端数据级权限控制的基本概念:页面资源甚至逻辑都应纳入权限控制,分离出岗位的概念用于前端控制实现

作者主要针对的是前端实现,因为作者看到很多系统明明在表结构上已经做到资源级的权限控制,但给到前端的依然是只有角色一个概念在玩,导致出现很多冗余的判断区分,维护起来也很麻烦。如果把资源控制相关的内容全部塞到角色下面,依然也是茫茫多的判断。只针对岗位这个概念上做判断,逻辑上会清晰很多

相关推荐
辻戋7 分钟前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保9 分钟前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun1 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp1 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.2 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl4 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫6 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友6 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理7 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻7 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js