Vue项目基于RBAC模型的权限控制实现

1,101 阅读3分钟

前言

在前端项目开发中,大多数的中后台项目都需要做权限控制。权限一般分为页面权限、操作权限、数据权限,这些权限再和角色账号绑定,组合成复杂的权限模型。本文将介绍在Vue中页面权限和操作权限的实现方法,数据权限大多都是后端控制所以滤过。


RBAC权限模型

RBAC(Role Based Access Control,基于角色的访问控制),用户通过角色与权限进行关联。一个账号关联多个角色,一个角色可以关联多个权限,进而构成用户-角色-权限模型。


权限初始化

在用户登录后,我们首先需要获取用户配置信息,如角色、token等,在根据角色获取权限信息,如:菜单、访问页面权限、按钮操作权限。获取这个信息后将它存入Vuex中,以便后续读取。


页面权限控制

页面权限控制某一角色能否访问前端哪些页面,对应Vue工程就是前端路由。当角色访问不在权限范围内的页面时,前端页面应该重定向到一个403页面。在Vue中我用到的页面权限控制有两种方式,都是Vue-Router提供的方法:路由的全局钩子和动态添加路由

  1. 路由的全局钩子

    Vue-Router为我们提供了很多路由导航的钩子beforeEach(全局前置守卫)、beforeResolve(全局解析守卫)、afterEach(全局后置钩子)等等。
    beforeEach:当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。通过这个钩子是我们可以实现页面的权限控制。首先我们需要注册一个前置守卫,其中回调函数的三个参数详情查看全局前置守卫官方文档。

import store from '@/store';
router.beforeEach((to, from, next) => {
  const { token, whiteList } = store.state; // 获取登录信息和页面访问白名单
  const { path } = to;
  const isToLoginPage = path === '/login';
  if (!!token) {
    if (isToLoginPage) {
      //已登录,重定向到首页
      next({ path: '/' });
    } else {
      const accessible = whiteList.includes(path); //是否能访问
      const dest = accessible ? undefined : { path: '/403' };
      next(dest);
    }
  } else { // 未登
    const dest = isToLoginPage ? undefined : { path: '/login' };
    next(dest);
  }
});
  1. 动态添加路由

    Vue-router除了使用钩子函数实现权限控制外,我们还能使用addRoute达到同样的效果。
    addRoute:添加一条新路由规则。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。 在用户未登录时,我们首先初始一些公共的路由,如:登录、404、403等页面路由

import VueRouter from 'vue-router'
const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
      { path: '/login', name: 'Login', component: 'XXX' },
      { path: '/403', name: '403',component: 'XXX' },
      { path: '*',name: 'NotFound', component: 'XXX' }
    ]
});
export default router;

初始完成公共路由后,当用户登录后,再获取账号信息:账号绑定的角色以及对应的页面权限


操作权限控制

操作权限暂时我所接触到的大多都是和按钮进行绑定的,指某一角色对某一按钮是否可见或者禁用。这里也介绍两种实现方法。首先我们需要获取到角色对应的操作权限

  1. 对按钮组件进行重写

    这种方法首先你的按钮都是用的某一组件库的按钮组件,比如我用的是element-ui,在启动vue之前,获取按钮组件的props和render方法,然后用对props和render方法改写。

import store from '@/store';
import { Button } from 'element-ui';
const { props, render } = Button;

Button.props = {
  ...props,
  // 按钮操作类型
  actionType: {
    type: String,
    default: ''
  }
};
Button.render = function () {
  // 获取角色可操作权限列表
  const { operationTable } = store.state;
  const { actionType } = this;
  // 是否具有操作权限
  if (operationTable.includes(actionType) || !actionType) {
    return render.call(this, ...arguments);
  }else{
    return null;
  }
};

在模板中使用

<!--当按钮具有编辑权限时才会显示-->
<el-button actionType='eidt' type='primary'>编辑</el-button>
  1. 使用自定义指令
import store from '@/store';
Vue.directive('actionType', {
  inserted(el, { value }) {
    const { operationTable } = store.state;
    // 是否具有操作权限
    if (operationTable.includes(value) && !value) {
      el.parentNode.removeChild(el); //移除当前按钮
    }
  }
});

在模板中使用

<!--当按钮具有编辑权限时才会显示-->
<el-button v-actionType='eidt' type='primary'>编辑</el-button>