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

定义

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自公司的账单审查"。因为产品也大概率会加一个字段用于区分不同的数据来源,而现在只需要切换到相应的岗位就能确定使用人员看到与操作的数据权限范围

总结

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

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

相关推荐
腾讯TNTWeb前端团队38 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试