后台管理系统的权限管理,前端你们用的哪种方案?

2021-07-05 10:05:52 +08:00
 csdoker

(以下两种权限管理的方法均基于 RBAC 模型)

一、权限列表法

  1. 后端在每个角色下挂载其具有权限的路由(菜单)和按钮数据,然后根据当前用户角色,返回给前端对应的角色数据(菜单、按钮权限等)
  2. 前端拿到菜单数据 menus 后,将其处理为树状结构,然后生成路由、侧边栏菜单(也可以直接由后端返回树状的 menus 数据)
  3. 页面元素可以通过 checkPermission 方法,判断当前元素是否有权限
// 用户数据
const users = [
  {
    userId: 1,
    username: 'admin',
    password: '123456',
    phone: '13600000000',
    email: 'admin@react.com',
    desc: '超级管理员',
    roles: [1]
  },
  {
    userId: 2,
    username: 'user',
    password: '123456',
    phone: '13600000001',
    email: 'user@react.com',
    desc: '运维人员',
    roles: [2]
  }
]
// 角色数据
const roles = [
  {
    roleId: 1,
    title: '超级管理员',
    desc: '超级管理员',
    menus: [
      {
        menuId: 1,
        title: '首页',
        icon: 'icon-home',
        url: '/home',
        desc: '首页',
        parentId: null,
        children: null
      },
      {
        menuId: 2,
        title: '系统管理',
        icon: 'icon-setting',
        url: '/system',
        desc: '系统管理目录分支',
        parentId: null,
        children: [
          {
            menuId: 3,
            title: '用户管理',
            icon: 'icon-user',
            url: '/system/useradmin',
            desc: '系统管理 /用户管理',
            parent: 2,
            children: null
          },
          {
            menuId: 4,
            title: '角色管理',
            icon: 'icon-team',
            url: '/system/roleadmin',
            desc: '系统管理 /角色管理',
            parent: 2,
            children: null
          },
          {
            menuId: 5,
            title: '权限管理',
            icon: 'icon-safetycertificate',
            url: '/system/poweradmin',
            desc: '系统管理 /权限管理',
            parent: 2,
            children: null
          },
          {
            menuId: 6,
            title: '菜单管理',
            icon: 'icon-appstore',
            url: '/system/menuadmin',
            desc: '系统管理 /菜单管理',
            parent: 2,
            children: null
          }
        ]
      },
    ],
    powers: [
      {
        powerId: 1,
        menuId: 3,
        title: '新增',
        code: 'user:add',
        desc: '用户管理 - 添加权限'
      },
      {
        powerId: 2,
        menuId: 3,
        title: '修改',
        code: 'user:up',
        desc: '用户管理 - 修改权限'
      },
      {
        powerId: 3,
        menuId: 3,
        title: '查看',
        code: 'user:query',
        desc: '用户管理 - 查看权限'
      },
      {
        powerId: 4,
        menuId: 3,
        title: '删除',
        code: 'user:del',
        desc: '用户管理 - 删除权限'
      },
      {
        powerId: 5,
        menuId: 3,
        title: '分配角色',
        code: 'user:role',
        desc: '用户管理 - 分配角色权限'
      },
      {
        powerId: 6,
        menuId: 4,
        title: '新增',
        code: 'role:add',
        desc: '角色管理 - 添加权限'
      },
      {
        powerId: 7,
        menuId: 4,
        title: '修改',
        code: 'role:up',
        desc: '角色管理 - 修改权限'
      },
      {
        powerId: 8,
        menuId: 4,
        title: '查看',
        code: 'role:query',
        desc: '角色管理 - 查看权限'
      },
      {
        powerId: 18,
        menuId: 4,
        title: '分配权限',
        code: 'role:power',
        desc: '角色管理 - 分配权限'
      },
      {
        powerId: 9,
        menuId: 4,
        title: '删除',
        code: 'role:del',
        desc: '角色管理 - 删除权限'
      },
      {
        powerId: 10,
        menuId: 5,
        title: '新增',
        code: 'power:add',
        desc: '权限管理 - 添加权限'
      },
      {
        powerId: 11,
        menuId: 5,
        title: '修改',
        code: 'power:up',
        desc: '权限管理 - 修改权限'
      },
      {
        powerId: 12,
        menuId: 5,
        title: '查看',
        code: 'power:query',
        desc: '权限管理 - 查看权限'
      },
      {
        powerId: 13,
        menuId: 5,
        title: '删除',
        code: 'power:del',
        desc: '权限管理 - 删除权限'
      },
      {
        powerId: 14,
        menuId: 6,
        title: '新增',
        code: 'menu:add',
        desc: '菜单管理 - 添加权限'
      },
      {
        powerId: 15,
        menuId: 6,
        title: '修改',
        code: 'menu:up',
        desc: '菜单管理 - 修改权限'
      },
      {
        powerId: 16,
        menuId: 6,
        title: '查看',
        code: 'menu:query',
        desc: '菜单管理 - 查看权限'
      },
      {
        powerId: 17,
        menuId: 6,
        title: '删除',
        code: 'menu:del',
        desc: '菜单管理 - 删除权限'
      }
    ]
  },
  {
    roleId: 2,
    title: '运维人员',
    desc: '运维人员',
    menus: [
      {
        menuId: 1,
        title: '首页',
        icon: 'icon-home',
        url: '/home',
        parent: null,
        desc: '首页'
      }
    ],
    powers: []
  }
]
// 判断当前元素是否有权限(按钮级)
const checkPermission = powerCode => {
  return powers.map(item => item.code).includes(powerCode)
}
checkPermission('menu:add') && <Button>新增</Button>

还有两个小问题我认为需要注意下:

路由和权限数据获取时机

这里可以进行一些接口的拆分,比如把路由和权限数据单独拆成一个接口来获取(/getMenusAndPowers,其实也就是当前角色对应的数据)

在每次路由渲染之前(也就是路由拦截逻辑中),判断当前 store 中有没有角色数据,如果没有就重新请求 /getMenusAndPowers 接口,获取最新的角色数据并存入 store 中;如果已经有数据,就什么都不做(避免重复请求)

这样做我认为有个好处,就是当用户修改了权限或者菜单数据后,刷新页面后,会去重新拉取最新的数据; 有的系统的做法是登陆以后请求一次接口,然后把权限和菜单数据存在 storage 中,但这样每次修改数据后,用户必须重新登录系统才能看到最新的效果,体验不好

路由数据拼接

路由可以分为不需要根据角色来判断权限的( constantRoutes,如 login 、404 等页面路由)路由和需要权限的( asyncRoutes,后端返回的就是这部分),前端最终的路由数据应该由这两部分组装

一般的前后端分离项目在路由拦截里还需要添加判断用户是否登录的逻辑

角色类型法

此方法参考项目: https://github.com/panjiachen/vue-element-admin

  1. 前端把角色类型写死,并把角色 ID 绑定到路由(菜单)数据中,然后过滤当前角色有权限访问的路由,由前端来维护这份路由数据
  2. 这种方法最大的缺点是当系统的角色类型有变化时,前端需要手动修改这份数据,不够灵活(个人观点
const menus = [
  {
    title: '',
    path: '/login',
    roles: ['admin', 'test'],
    component: '@/page/user/Login'
  },
  {
    title: '',
    path: '/404',
    roles: ['test'],
    component: '@/page/404'
  },
  {
    title: '',
    path: '/home',
    roles: ['admin'],
    component: '@/page/home'
  }
]

看了很多后台项目,基本都是这两种方案的思路,我个人觉得第一种方案要更靠谱点?大家项目都是用的哪种呢?

2187 次点击
所在节点    问与答
10 条回复
basefas
2021-07-05 10:17:09 +08:00
全部交给后端做,前端不要判断权限
Aliennnnnn
2021-07-05 10:30:15 +08:00
第一种
csdoker
2021-07-05 10:30:19 +08:00
@basefas 就是第一种方案吧?
basefas
2021-07-05 10:34:49 +08:00
@csdoker #3 对的,前端只做数据展示
hellwys1
2021-07-05 15:16:09 +08:00
第一种。
第二种不靠谱,但是小项目前端也这样做过。
csdoker
2021-07-05 15:54:28 +08:00
@hellwys1 好的👌
rsyjjsn
2021-07-06 13:23:13 +08:00
理论第一种更优
实际第二种更方便(毕竟代码要自己写,antd 的中台目前也是第二种)
csdoker
2021-07-06 14:14:26 +08:00
@luoyelusheng 第二种就是前端的工作量要大点,需要根据角色去手动改一些配置
rsyjjsn
2021-07-07 09:44:54 +08:00
@csdoker 这就得看你的项目初期架构怎么样了,如果公司有成熟的技术栈,那么像这种配置基本上就是拿来随便改改就行了,反而是第一种,你无法保证后端返回权限符合你当前前端页面排版,毕竟后端重架构,前台重交互(这也是我经常和后端开架的理由)
lizy0329
76 天前
为什么要“前端把角色类型写死”? 都数据 fetch 下来 match 一下不就完了吗

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/787541

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX