RBAC权限详解

1.传统的权限设计

首先,我们先了解下什么是传统的权限设计

RBAC权限详解_第1张图片

从上面的图中,我们发现,传统的权限设计是对每个人进行单独的权限设置,但这种方式已经不适合目前企业的高效管控权限的发展需求,因为每个人都要单独去设置权限 

2. RBAC权限详解

基于此,RBAC的权限模型就应运而生了,RBAC(Role-Based Access control) ,也就是基于角色的权限分配解决方案,相对于传统方案,RBAC提供了中间层Role(角色),其权限模式如下

RBAC权限详解_第2张图片

 RBAC实现了用户和权限点的分离,想对某个用户设置权限,只需要对该用户设置相应的角色即可,而该角色就拥有了对应的权限,这样一来,权限的分配和设计就做到了极简,高效,当想对用户收回权限时,只需要收回角色即可,接下来,我们就在该项目中实施这一设想

3. 权限设置

1.权限点

权限:在一个系统内是否具有做某个操作的权利
权限分为两个级别

            1. 菜单权限:是否有权限访问某个菜单
            2. 按钮权限:是否有权限操作 页面上的某个按钮功能

2.业务逻辑

对于权限数据来说,有两个级别的设置

1.能不能访问谋个页面

2.在页面上,能不能操作某个按钮

3.RBAC权限设计思想

目标:不同账号登录系统后看到不同的页面, 能执行不同的功能

模式如下

RBAC权限详解_第3张图片

三个关键点:

用户: 就是使用系统的人

权限点:这个系统中有多少个功能(例始:有3个页面,每个页面上的有不同的操作)

角色:不同的权限点的集合

RBAC权限详解_第4张图片

 

实际上就是

  1. 给用户分配角色
  2. 给角色分配权限点

实际业务中:

  1. .先给员工分配一个具体的角色
  2. 然后给角色分配具体的权限点 (工资页面 工资页面下的操作按钮)员工就拥有了权限点

4.权限具体业务

RBAC权限详解_第5张图片

RBAC权限详解_第6张图片

4. 具体步骤

1.动态生成左侧菜单-addRoutes方法

在router中直接静态写死的动态路由表改造成通过addRoutes 方法调用添加的形式

// 引入所有的动态路由表(未经过筛选)
import router, { asyncRoutes } from '@/router'
const whiteList = ['/login', '/404']
router.beforeEach(async(to, from, next) => {
  NProgress.start() // 启动进度条
  if (store.getters.token) {
    if (to.path === '/login') {
      next('/')
    } else {
      if (!store.getters.userId) {
        // 判断userInfo有没有id值,如果没有就进user/getUserInfo
        const menus = await store.dispatch('user/getUserInfo')
        console.log('当前用户可以访问的权限是', menus)
        // 根据用户的实际权限menus,可以在asyncRoutes筛选出用户可以访问的权限
        const filterRoute = asyncRoutes.filter(route => {
          return menus.includes(route.children[0].name)
        })
 
        // 因为404页面在路由的中间位置,要进去之前404路由后面的路由时,直接进404页面了
        // 把404路由添加到所有路由的末尾就可以解决这个问题
        filterRoute.push( // 404 page must be placed at the end !!!
          { path: '*', redirect: '/404', hidden: true })
        // 改写成动态添加路由
        // addRoutes用来动态添加路由配置
        // 只有在这里设置了补充路由配置,才能去访问页面,如果没有设置的话,左边的菜单不显示的
        router.addRoutes(filterRoute)
 
        // 把他们保存到vuex中,在src\layout\components\Sidebar\index.vue
        // 生成左侧菜单时,也应该去vuex中拿
        store.commit('menu/setMenuList', filterRoute)
        // 解决刷新出现的白屏bug
        next({
          ...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
          replace: true // 重进一次, 不保留重复历史
        })
      } else {
        next()
      }
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
})

我们发现左侧的菜单只剩下静态的首页了,浏览器手动输入某一个动态路由地址,依旧是可用的,这证明我们其实已经把动态路由添加到我们的路由系统了。

2.动态生成左侧菜单-改写菜单保存位置

当前的菜单渲染使用的数据:this.r o u t e r . o p t i o n s . r o u t e s 这 个 数 据 是 固 定 , a d d R o u t e s 添 加 的 路 由 表 只 存 在 内 存 中 , 并 不 会 改 变 t h i s . router.options.routes 这个数据是固定,addRoutes添加的路由表只存在内存中,并不会改变this.router.options.routes这个数据是固定,addRoutes添加的路由表只存在内存中,并不会改变this.router.options.routes

目标:调用router.addRoutes() , 想要数据反映到视图上, 将路由信息存在vuex中

RBAC权限详解_第7张图片

2.1定义vuex管理菜单数据

在src/store/modules下补充menu.js :

 

// 导入静态路由
import { constantRoutes } from '@/router'
export default {
  namespaced: true,
  state: {
    // 先以静态路由作为菜单数据的初始值
    menuList: [...constantRoutes]
  },
  mutations: {
    setMenuList(state, asyncRoutes) {
      // 将动态路由和静态路由组合起来
      state.menuList = [...constantRoutes, ...asyncRoutes]
    }
  }
}

src/store/index.js中注册这个模块

2.2提交setMenuList生成完整的菜单数据

修改src/permission.js:

if (!store.getters.userId) {
    await store.dispatch('user/getUserInfo')
    // 把动态路由数据交给菜单
    store.commit('menu/setMenuList', asyncRoutes)
    // 把动态路由添加到应用的路由系统里
    router.addRoutes(asyncRoutes)
}

2.3菜单生成部分改写使用vuex中的数据

routes() {
  // 拿到的是一个完整的包含了静态路由和动态路由的数据结构
  // return this.$router.options.routes
  return this.$store.state.routeMenu.menuList
}

3.使用权限数据做过滤处理

  1. 通过后台返回的权限数据, 过滤出要显示的菜单, 过滤使用路由的name作为标识
  2. 从action中获取返回值
    action本质上是一个promise 它的return 结果可以通过const res = await action名来接收

RBAC权限详解_第8张图片

3.store/modules/user.js

 

// 用来获取用户信息的action
    async getUserInfo(context) {
      // 1. ajax获取基本信息,包含用户id
      const rs = await getUserInfoApi()
      console.log('用来获取用户信息的,', rs)
      // 2. 根据用户id(rs.data.userId)再发请求,获取详情(包含头像)
      const info = await getUserDetailById(rs.data.userId)
      console.log('获取详情', info.data)
      // 把上边获取的两份合并在一起,保存到vuex中
      context.commit('setUserInfo', { ...info.data, ...rs.data })
      return rs.data.roles.menus
    },

4.在permission.js中过滤

if (!store.getters.userId) {
        // 有token,要去的不是login,就直接放行
        // 进一步获取用户信息
        // 发ajax---派发action来做
        const menus = await store.dispatch('user/getUserInfo')
        console.log('当前用户能访问的页面', menus)
        console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)
        // 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面
 
        const filterRoutes = asyncRoutes.filter(route => {
          const routeName = route.children[0].name
          return menus.includes(routeName)
        })
 
        // 一定要在进入主页之前去获取用户信息
 
        // addRoutes用来动态添加路由配置
        // 只有在这里设置了补充了路由配置,才可能去访问页面
        // 它们不会出现左侧
        router.addRoutes(filterRoutes)
 
        // 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue
        // 生成左侧菜单时,也应该去vuex中拿
        store.commit('menu/setMenuList', filterRoutes)
 
        // 解决刷新出现的白屏bug
        next({
          ...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
          replace: true // 重进一次, 不保留重复历史
        })
      }

注意事项

  • 解决404问题
    原因:现在我们的路由设置中的404页处在中间位置而不是所有路由的末尾了
    解决办法:把404页改到路由配置的最末尾就可以了
    \1. 从route/index.js中的静态路由中删除path:’*'这一项
    \2. 在permission.js中补充在最后

代码示例

    if (!store.getters.userId) {
        // 有token,要去的不是login,就直接放行
        // 进一步获取用户信息
        // 发ajax---派发action来做
        const menus = await store.dispatch('user/getUserInfo')
        console.log('当前用户能访问的页面', menus)
        console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)
        // 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面
 
        const filterRoutes = asyncRoutes.filter(route => {
          const routeName = route.children[0].name
          return menus.includes(routeName)
        })
 
        // 一定要在进入主页之前去获取用户信息
 
        // 把404加到最后一条
        filterRoutes.push( // 404 page must be placed at the end !!!
          { path: '*', redirect: '/404', hidden: true })
 
        // addRoutes用来动态添加路由配置
        // 只有在这里设置了补充了路由配置,才可能去访问页面
        // 它们不会出现左侧
        router.addRoutes(filterRoutes)
 
        // 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue
        // 生成左侧菜单时,也应该去vuex中拿
        store.commit('menu/setMenuList', filterRoutes)
 
        // 解决刷新出现的白屏bug
        next({
          ...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
          replace: true // 重进一次, 不保留重复历史
        })
      } 
  • 退出时重置路由

router/index.js中

// 重置路由
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // 重新设置路由的可匹配路径
}

这个方法就是将路由重新实例化,相当于换了一个新的路由,之前**加的路由就不存在了,需要在登出的时候, 调用一下即可**

import { resetRouter } from '@/router'
// 退出的action操作
logout(context) {
  // 1. 移除vuex个人信息
  context.commit('removeUserInfo')
  // 2. 移除token信息
  context.commit('removeToken')
  // 3. 重置路由
  resetRouter()
  // 4. 重置 vuex 中的路由信息 只保留每个用户都一样的静态路由数据
  //    在moudules中的一个module中去调用另一个modules中的mutation要加{root:true}
  context.commit('setMenuList', [], { root: true })
}

4.控制操作按钮

RBAC权限详解_第9张图片

定义全局检测的方法:

Vue.prototype.$checkPoint = function(pointKey) {
  if (store.state.user.userInfo.roles.points) {
    // 进行权限点判断
    return store.state.user.userInfo.roles.points.includes(pointKey)
  }
  // 没有权限点POINTS信息, 说明用户没有身份, 没有任何权限
  return false

在模板中通过if来控制按钮显示


$checkPoint中的参数以系统中权限点的标识符为准。

或者自定义指令控制按钮显示

在main.js中定义全局指令

// 注册一个全局自定义指令 `v-allow`
Vue.directive('allow', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function(el, binding) {
    // v-focus="'abc'"  ===> binding.value = 'abc'
    if (store.state.user.userInfo.roles.points.includes(binding.value)) {
      // 元素是可见的
    } else {
      el.style.display = 'none'
    }
  }

使用

导入excel

 

你可能感兴趣的:(前端,javascript,网络)