一、理解权限控制
- 前端做权限控制,主要是为了满足不同租户使用系统,能看到的系统页面和操作功能有所区别,特定的用户只能访问权限范围内的资源,从而实现系统的权限制衡,或者根据用户的付费等级,进行功能的使用限制。
- 权限控制可以大体分为两种,【操作/接口】级别、【页面/路由】级别。
- 【操作/接口】级别:即页面上的【新增】【编辑】【删除】【查询】等按钮的操作,页面上的操作,同时对应接口的增删改查操作,可以录入同一份数据,同时对应接口/操作。
- 【页面/路由】级别:系统的页面对应前端层面的路由控制,所以有人会把它称作【页面权限】,也有人称它为【路由权限】。
二、实现权限控制
- 实现【操作/接口】权限
- 在操作权限模块,录入系统所有的接口信息,主要有接口的【名称】和【权限标识】。
- 在配置角色的时候,勾选当前角色拥有的接口访问/按钮操作权限,用户关联角色,这样就可以获取到每个用户的接口权限,如图所示。
- 用户登陆成功后(注意:有部分接口是不需要权限控制的,像登陆、验证码、用户信息等必须访问的接口,以及一些业务上不必要添加权限的接口),请求后端接口,返回当前用户所有的接口权限信息,并存储到vuex中(这里存到了permissionButtons)。
- 通过自定义指令,判断当前按钮的key是否存在permissionButtons数组中,如果存在,则有权限,显示按钮,如果不存在,则当前用户没有该按钮的操作权限,在页面上隐藏当前按钮。
// 自定义permission指令 import Vue from 'vue'; import store from '@/store'; Vue.directive('permission', { inserted(el, binding) { const permissionCode = binding.value; if (permissionCode) { const permissionButtons = store.getters.permissionButtons; if (_.indexOf(permissionButtons, permissionCode) === -1) { el.parentNode.removeChild(el); } } } }); // 页面按钮权限绑定 <el-button v-permission="'system:role:add'">新增</el-button>
- 接口的权限需要后端配合处理,前端隐藏按钮,只是视图层面的控制,如果用户通过postman等工具或其他途径直接访问接口,还是会绕开视图层面的限制,访问接口,所以,需要后端配合进行接口层级上的处理。后端需要把当前用户的权限经过处理,生成对应token,前端携带token访问,后端通过处理token,判断当前用户是否有该接口的访问权限,如果有,则允许访问,否则拒绝访问,从而实现接口层级的权限控制。
- 实现【页面/路由】权限
- 在菜单权限模块,录入系统所有需要控制权限的页面,主要有页面的【名称】【路由地址】【组件位置】【图标】等信息。
- 同样和【操作/接口】权限一样,通过给角色配置页面权限,用户关联角色,即可获取到当前用户的所有页面权限。
- 同样在用户登陆成功后,获取当前用户所有页面权限,并对路由进行动态注册(注意:有部分页面是不需要动态注册,所有用户都可以访问的,像登陆页面、404页面、403页面等,可以写死到代码中,其类似于白名单),返回的所有路由信息放到permissionMenus。
import Vue from 'vue'; import VueRouter from 'vue-router'; import store from '@/store'; import {getToken} from '@/utils/storage'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; NProgress.configure({showSpinner: false}); Vue.use(VueRouter); export const constantRoutes = [ { path: '/login', component: () => import('@/pages/login'), hidden: true } ]; const createRouter = () => new VueRouter({ routes: constantRoutes }); const router = createRouter(); const whiteList = ['/login']; router.beforeEach((to, from, next) => { NProgress.start(); if (getToken()) { // 如果登陆后 还跳转至登陆页 则跳转到系统默认页 if (to.path === '/login') { next({path: '/'}); } else { if (_.isEmpty(store.getters.userInfo)) { // 获取用户信息,处理当前用户路由信息 store.dispatch('user/getUserInfo').then(() => { const newRouter = createRouter(); router.matcher = newRouter.matcher; const getRouter = store.getters.permissionMenus; getRouter.push({path: '*', redirect: '/'}); router.options.routes = [...constantRoutes, ...getRouter]; router.addRoutes(getRouter); store.dispatch('user/getUserFieldConfig').then(res => { next({...to, replace: true}); }).catch(() => { next({path: '/login', replace: true}); }); }).catch(err => { store.dispatch('user/logout'); }); } else { next(); } } } else { // 没有token if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 next(); } else { // 否则全部重定向到登录页 next(`/login?redirect=${to.fullPath}`); } } }); router.afterEach(() => { NProgress.done(); window.scrollTo(0, 0); }); export default router;
三、小结
权限的控制实现还有很多方法,这里只是列举了其中一种。