基于 RBAC 的后台权限管理

132 阅读3分钟

基本概念

RBAC(Role-Based Access Control)是指 基于角色控制用户访问。通过给用户分配不同的角色,不同的角色拥有不同的权限,用户根据权限来查看不同的页面或者功能按钮。

整个权限系统分成了两部分:

  1. 页面权限:当前用户可以访问的页面
  2. 功能权限:当前用户可以使用的功能按钮

实现逻辑:

  1. 页面权限:根据不同权限数据,利用 addRoute API 动态配置路由对象。
  2. 功能权限:根据不同权限数据,利用 自定义指令 实现隐藏功能按钮。

具体实现:

页面权限代码实现:
  1. 定义专门处理权限路由的模块
import { publicRoutes, privateRoutes } from '@/router'
export default {
  namespaced: true,
  state: {
    // 路由表:初始拥有静态路由权限
    routes: publicRoutes
  },
  mutations: {
    // 增加路由
    setRoutes(state, newRoutes) {
      // 永远在静态路由的基础上增加新路由
      state.routes = [...publicRoutes, ...newRoutes]
    }
  },
  actions: {
    // 根据权限筛选路由
    filterRoutes(context, menus) {
      const routes = []
      // 路由权限匹配
      menus.forEach((key) => {
        // 为每个私有路由指定一个 name , 每个 name 对应一个页面权限
        // 通过 name 与 当前用户返回的 页面权限 数据进行匹配筛选
        routes.push(...privateRoutes.filter((item) => item.name === key))
      })
      context.commit('setRoutes', routes)
      return routes
    }
  }
}
  1. 在全局路由前置守卫中,获取用户当前页面权限数据后,筛选出用户需要添加的页面权限 。利用 addRoute() 循环添加。
import router from './router'
import store from './store'

// 白名单
const whiteList = ['/login']

router.beforeEach(async (to, from, next) => {
  if (store.getters.token) {
    if (to.path === '/login') {
      next('/')
    } else {
      // 判断用户数据是否已获取
      // 若不存在用户信息,则需要获取用户信息
      if (!store.getters.hasUserInfo) {
        // 触发获取用户信息的 action,并获取用户当前权限
        const { permission } = await store.dispatch('user/getUserInfo')
        // 处理用户权限,筛选出需要添加的权限
        const filterRoutes = await store.dispatch(
          'permission/filterRoutes',
          permission.menus
        )
        // 利用 addRoute 循环添加
        filterRoutes.forEach((item) => {
          router.addRoute(item)
        })
        // 添加完动态路由之后,需要在进行一次主动跳转
        return next(to.path)
      }
      next()
    }
  } else {
    if (whiteList.indexOf(to.path) > -1) {
      next()
    } else {
      next('/login')
    }
  }
})
  1. 退出登录时,重置路由表数据,防止下次登录后,权限数据未刷新(需要强制刷新)。

    利用 removeRoute()按名称删除路由

// 在路由表中定义 初始化路由表
export function resetRouter() {
  if (
    store.getters.userInfo &&        // 用户数据
    store.getters.userInfo.permission &&  // 用户权限数据
    store.getters.userInfo.permission.menus  // 用户权限中 页面权限
  ) {
    const menus = store.getters.userInfo.permission.menus
    menus.forEach((menu) => {
      router.removeRoute(menu)
    })
  }
}
// 退出登录时,触发该方法
功能权限代码实现:
  1. 通过 自定义指令 实现,在需要权限的标签上,通过指令传递权限的 key。 eg:v-permission="['importUser']"
  2. 然后在自定义指令中,根据按钮权限数据进行匹配。不匹配就移除 DOM。(parentNode.removeChild(domElement))
import store from '@/store'

function checkPermission(el, binding) {
  // 获取绑定的值
  const { value } = binding
  // 获取用户数据中的功能权限数据
  const points = store.getters.userInfo.permission.points
  if (value && value instanceof Array) {
    // 匹配对应的指令
    const hasPermission = points.some((point) => {
      return value.includes(point)
    })
    if (!hasPermission) {
      el.parentNode && el.parentNode.removeChild(el)
    }
  } else {
    throw new Error('v-permission value is ["admin","editor"]')
  }
}

export default {
  // 在绑定元素的组件被挂载时调用
  mounted(el, binding) {
    checkPermission(el, binding)
  },
  // 在包含组件的 vNode 及其 子组件的 vNode 更新后调用
  update(el, binding) {
    checkPermission(el, binding)
  }
}
  1. directives/index 中绑定该指令
...
import permission from './permission'

export default (app) => {
  ...
  app.directive('permission', permission)
}