How Vue performs permission management

reference documents

Interviewer: How should Vue do permission management? How to control the permissions to the button level?

Rights Management Classification

In the final analysis, the front-end authority is the right to initiate the request, and there are generally two trigger methods

  • Page loading trigger (page refresh, page jump)
  • Triggered by user events (actions such as button clicks)

The purpose of permissions that need to be achieved at the front end:

  • In terms of routing, users can only see the navigation menu that they have access to when they log in, and can only access the routing addresses that they have access to
  • In terms of views, users can only see the resources they have the right to see and the controls they have the right to operate
  • In terms of interface, if routing, view, etc. are misconfigured and forget to add permissions, you can intercept unauthorized requests when initiating requests

Therefore, front-end authority management can be divided into four categories:

  • Interface permissions
  • routing authority
  • menu permissions
  • View Permissions (Button Permissions)

Interface permissions

It is widely used in Vue projects axiosfor front-end and back-end interaction, and cooperates with the return of the back-end jwtto control the authority of the interface

The user gets it after logging in token, axiosand adds it in the request interceptor token. If it is tokeninvalid or expired, it will be processed accordingly.

axios.interceptors.request.use(config => {
    
    
    config.headers['token'] = cookie.get('token')
    return config
})

routing authority

Option One

(metaAll routes are mounted during initialization, and the corresponding permission information is marked in the meta information in the route . By setting the global route guard, a verification is performed before each route jump.

const routerMap = [
  {
    
    
    path: '/permission',
    component: Layout,
    redirect: '/permission/index',
    alwaysShow: true, // will always show the root menu
    meta: {
    
    
      title: 'permission',
      icon: 'lock',
      roles: ['admin', 'editor'] // you can set roles in root nav
    },
    children: [{
    
    
      path: 'page',
      component: () => import('@/views/permission/page'),
      name: 'pagePermission',
      meta: {
    
    
        title: 'pagePermission',
        roles: ['admin'] // or you can only set roles in sub nav
      }
    }, {
    
    
      path: 'directive',
      component: () => import('@/views/permission/directive'),
      name: 'directivePermission',
      meta: {
    
    
        title: 'directivePermission'
        // if do not set roles, means: this page does not require permission
      }
    }]
  }]

This method has the following four disadvantages:

  • Load all routes. If there are many routes and users do not have permission to access all routes, performance will be affected.
  • In the global routing guard, authority judgment is required for each routing jump.
  • The menu information is hard-coded on the front end. To change the displayed text or permission information, it needs to be recompiled
  • The menu is coupled with the route. When defining the route, the menu display title, icon and other information are added, and the route is not necessarily displayed as a menu, but additional fields are required for identification

Option II

When initializing, first mount routes that do not require permission control, such as login pages, 404 and other error pages. If the user performs mandatory access through the URL, it will directly enter 404, which is equivalent to controlling from the source. .

addRoutesAfter logging in, get the user's permission information, then filter the routes that have permission to access, and call to add routes in the global route guard

import router from './router'
import store from './store'
import {
    
     Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import {
    
     getToken } from '@/utils/auth' // getToken from cookie

NProgress.configure({
    
     showSpinner: false })// NProgress Configuration

// permission judge function
function hasPermission(roles, permissionRoles) {
    
    
  if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
  if (!permissionRoles) return true
  return roles.some(role => permissionRoles.indexOf(role) >= 0)
}

const whiteList = ['/login', '/authredirect']// no redirect whitelist

router.beforeEach((to, from, next) => {
    
    
  NProgress.start() // start progress bar
  if (getToken()) {
    
     // determine if there has token
    /* has token*/
    if (to.path === '/login') {
    
    
      next({
    
     path: '/' })
      NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
    } else {
    
    
      if (store.getters.roles.length === 0) {
    
     // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetUserInfo').then(res => {
    
     // 拉取user_info
          const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']
          store.dispatch('GenerateRoutes', {
    
     roles }).then(() => {
    
     // 根据roles权限生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({
    
     ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch((err) => {
    
    
          store.dispatch('FedLogOut').then(() => {
    
    
            Message.error(err || 'Verification failed, please login again')
            next({
    
     path: '/' })
          })
        })
      } else {
    
    
        // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
        if (hasPermission(store.getters.roles, to.meta.roles)) {
    
    
          next()//
        } else {
    
    
          next({
    
     path: '/401', replace: true, query: {
    
     noGoBack: true }})
        }
        // 可删 ↑
      }
    }
  } else {
    
    
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) {
    
     // 在免登录白名单,直接进入
      next()
    } else {
    
    
      next('/login') // 否则全部重定向到登录页
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }
})

router.afterEach(() => {
    
    
  NProgress.done() // finish progress bar
})

On-demand mounting, routing needs to know the user's routing permissions, that is, when the user logs in, it needs to know which routing permissions the current user has

This method also has the following disadvantages:

  • In the global routing guard, each routing jump needs to be judged
  • The menu information is hard-coded on the front end. To change the displayed text or permission information, it needs to be recompiled
  • The menu is coupled with the route. When defining the route, the menu display title, icon and other information are added, and the route is not necessarily displayed as a menu, but additional fields are required for identification

menu permissions

Menu permissions can be understood as decoupling pages from routing

Option One

The front end defines the route, and the back end returns the menu

Front end defines routing information

{
    
    
    name: "login",
    path: "/login",
    component: () => import("@/pages/Login.vue")
}

nameThe field cannot be empty, and it needs to be associated with the menu returned by the backend according to this field. The menu information returned by the backend must have a namecorresponding field, and the uniqueness check must be done

Global Route Guard

function hasPermission(router, accessMenu) {
    
    
  if (whiteList.indexOf(router.path) !== -1) {
    
    
    return true;
  }
  let menu = Util.getMenuByName(router.name, accessMenu);
  if (menu.name) {
    
    
    return true;
  }
  return false;

}

Router.beforeEach(async (to, from, next) => {
    
    
  if (getToken()) {
    
    
    let userInfo = store.state.user.userInfo;
    if (!userInfo.name) {
    
    
      try {
    
    
        await store.dispatch("GetUserInfo")
        await store.dispatch('updateAccessMenu')
        if (to.path === '/login') {
    
    
          next({
    
     name: 'home_index' })
        } else {
    
    
          //Util.toDefaultPage([...routers], to.name, router, next);
          next({
    
     ...to, replace: true })//菜单权限更新完成,重新进一次当前路由
        }
      }  
      catch (e) {
    
    
        if (whiteList.indexOf(to.path) !== -1) {
    
     // 在免登录白名单,直接进入
          next()
        } else {
    
    
          next('/login')
        }
      }
    } else {
    
    
      if (to.path === '/login') {
    
    
        next({
    
     name: 'home_index' })
      } else {
    
    
        if (hasPermission(to, store.getters.accessMenu)) {
    
    
          Util.toDefaultPage(store.getters.accessMenu,to, routes, next);
        } else {
    
    
          next({
    
     path: '/403',replace:true })
        }
      }
    }
  } else {
    
    
    if (whiteList.indexOf(to.path) !== -1) {
    
     // 在免登录白名单,直接进入
      next()
    } else {
    
    
      next('/login')
    }
  }
  let menu = Util.getMenuByName(to.name, store.getters.accessMenu);
  Util.title(menu.title);
});

Router.afterEach((to) => {
    
    
  window.scrollTo(0, 0);
});

Every time the route jumps, the authority must be judged. The judgment here is also very simple, because the menu nameand the route nameare in one-to-one correspondence, and the menu returned by the backend has been filtered by the authority.

If namethe corresponding menu cannot be found according to the route, it means that the user has no permission to access

If there are many routes, you can only mount the routes that do not require permission control when the application is initialized. After obtaining the menu returned by the backend, filter out the accessible routes according to the corresponding relationship between the menu and the route, and dynamically addRoutesmount the

Option II

Routes and menus are all returned by the backend

Front-end unified definition components

const Home = () => import("../pages/Home.vue");
const UserInfo = () => import("../pages/UserInfo.vue");
export default {
    
    
    home: Home,
    userInfo: UserInfo
};

The backend returns a routing component of the form

[
    {
    
    
        name: "home",
        path: "/",
        component: "home"
    },
    {
    
    
        name: "home",
        path: "/userinfo",
        component: "userInfo"
    }
]

Before dynamically mounting the backend return route through addRoutes, you need to process the data and replace the component field with a real component

This solution requires a high degree of cooperation between the front and back ends

view permissions

Option One

v-ifShow or hide by controlling the button

Option II

Judgment of button permissions through custom instructions

First configure routing

{
    
    
    path: '/permission',
    component: Layout,
    name: '权限测试',
    meta: {
    
    
        btnPermissions: ['admin', 'supper', 'normal']
    },
    //页面需要的权限
    children: [{
    
    
        path: 'supper',
        component: _import('system/supper'),
        name: '权限测试页',
        meta: {
    
    
            btnPermissions: ['admin', 'supper']
        } //页面需要的权限
    },
    {
    
    
        path: 'normal',
        component: _import('system/normal'),
        name: '权限测试页',
        meta: {
    
    
            btnPermissions: ['admin']
        } //页面需要的权限
    }]
}

Custom permission authentication instruction

import Vue from 'vue'
/**权限指令**/
const has = Vue.directive('has', {
    
    
    bind: function (el, binding, vnode) {
    
    
        // 获取页面按钮权限
        let btnPermissionsArr = [];
        if(binding.value){
    
    
            // 如果指令传值,获取指令参数,根据指令参数和当前登录人按钮权限做比较。
            btnPermissionsArr = Array.of(binding.value);
        }else{
    
    
            // 否则获取路由中的参数,根据路由的btnPermissionsArr和当前登录人按钮权限做比较。
            btnPermissionsArr = vnode.context.$route.meta.btnPermissions;
        }
        if (!Vue.prototype.$_has(btnPermissionsArr)) {
    
    
            el.parentNode.removeChild(el);
        }
    }
});
// 权限检查方法
Vue.prototype.$_has = function (value) {
    
    
    let isExist = false;
    // 获取用户按钮权限
    let btnPermissionsStr = sessionStorage.getItem("btnPermissions");
    if (btnPermissionsStr == undefined || btnPermissionsStr == null) {
    
    
        return false;
    }
    if (value.indexOf(btnPermissionsStr) > -1) {
    
    
        isExist = true;
    }
    return isExist;
};
export {
    
    has}

Only the v-has directive needs to be referenced in the button used

<el-button @click='editClick' type="primary" v-has>编辑</el-button>

Guess you like

Origin blog.csdn.net/Dax1_/article/details/126999998