Vue background permission management

What are permissions

Permission is the permission to access specific resources. The so-called permission control means that users can only access the allocated resources. The front-end permission is generally a request for permission. The initiation of the request may be triggered by the following two forms

  • Page load trigger
  • Triggered by a button click on the page

In general, all request initiation is triggered by front-end routing or attempting. So we can start from these two aspects, and control the source of triggering permissions. The ultimate goal is to achieve

  • In terms of routing, after logging in, users can only see the navigation menu that they have access to, and can only access the routing address that they have access to, otherwise they will jump to the 4xx prompt page.
  • In terms of view, users can only see the content that they have the right to browse and the controls that they have the right to operate
  • Finally, add request control as the last line of defense. The button may be configured incorrectly. The button may have forgotten to add permissions. At this time, the request control can be used to cover up the unauthorized request will be intercepted on the front end.

Front end permissions

Front-end permission control can be divided into four aspects

  • Interface permissions
  • Button permissions
  • Menu permissions
  • Routing authority

Interface permission control

  • Interface permissions currently generally use jwt formal verification if it fails to return to 401, jump to the login page to log in again
  • Log in to complete to get the token to token when requested by axios interceptor to intercept each request carried in the header token
axios.interceptors.request.use(config => {
    
    
    config.headers['token'] = cookie.get('token')
    return config
})
axios.interceptors.response.use(res=>{
    
    },{
    
    response}=>{
    
    
    if (response.data.code === 40099 || response.data.code === 40098) {
    
     
    //token过期或者错误
        router.push('/login')
    }
})

Routing permission control

Option One,

Initialization is to mount all routes and mark the permission information of the response on the road to do verification 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
      }
    }]
  }]

There are disadvantages to this approach

  • Load all routes. If there are many routes and not all routes of the user have permission to access, it will affect performance.
  • In the global routing guard, permissions must be judged every time a route jumps
  • The menu information is hard-coded in the front end, and the display text or permission information needs to be recompiled.
  • When the menu and the route are coupled together to define the route, there is also information such as the menu display title icon and the route is not necessarily displayed as a menu, but more fields are added for identification.

Option II

When initializing, mount first without permission to control routing, such as login page 404 and other error pages. If the user compulsively accesses through the URL, it will go directly to 404, which is equivalent to controlling from the source.

After logging in, obtain user permission information and then filter the routes that have access to it. Call addRoutes to add routes in the global routing 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

// 权限判断功能
function hasPermission(roles, permissionRoles) {
    
    
  if (roles.indexOf('admin') >= 0) return true // admin 直接传递权限
  if (!permissionRoles) return true
  return roles.some(role => permissionRoles.indexOf(role) >= 0)
}
// 权限判断功能重定向白名单
const whiteList = ['/login', '/authredirect']

router.beforeEach((to, from, next) => {
    
    
  NProgress.start() // 开始进度条
  if (getToken()) {
    
     // 确定是否有令牌
    /* has token*/
    if (to.path === '/login') {
    
    
      next({
    
     path: '/' })
      NProgress.done() // 如果当前页面是dashboard,则每次钩子后都不会触发,所以请手动处理它
    } else {
    
    
      if (store.getters.roles.length === 0) {
    
     // 判断当前用户是否已拉取完user_info信息
      // 通过 
        store.dispatch('GetUserInfo').then(res => {
    
     // 拉取user_info
          const roles = res.data.roles // note: 角色必须是数组!这样的例如: ['editor','develop']
          store.dispatch('GenerateRoutes', {
    
     roles }).then(() => {
    
     // 根据roles权限生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({
    
     ...to, replace: true }) // hack方法 确保addRoutes已完成 ,设置replace:true,这样导航就不会留下历史记录
          })
        }).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() //如果当前页面为login,则每次钩子后都不会触发,所以手动处理它
    }
  }
})

router.afterEach(() => {
    
    
  NProgress.done() // 完成进度条
})

To mount routing on demand, you need to know the user's routing permissions, that is, those routing permissions that the current user has when the user logs in.

This method has the following disadvantages:

  • Every route jump in the global routing guard has to be judged
  • The menu information is hard-coded in the front end, and the display text or permission information needs to be recompiled.
  • When the menu is coupled with the route to define the route, there is also information such as the menu display title icon and the route is not necessarily displayed as a menu, but more fields are added for identification.

Menu control

Menu permissions can be understood as decoupling pages and routing

Option 1.
Menu and routing are separated. The menu is returned from the back-end, and the
front-end defines routing information

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

nameThe field cannot be empty. You need to associate this field with the back-end return menu. The menu information returned by the back-end must have a field corresponding to name and perform uniqueness verification
. Make judgments in the global routing 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);
});

The authority must be judged every time the route jumps. The judgement here is also very simple because the menu nameand the route are nameone-to-one, and the menu returned from the backend is already filtered by the authority.
If there are many routes, you can only hang it when the application is initialized. Load routes that do not require permission to obtain the menu returned from the backend and select the accessible routes according to the corresponding relationship between the menu and the route. Through addRoutesdynamic mounting

Disadvantages of this method:
* The menu needs to correspond to the routing one-to-one. New functions need to be added to the front end. New menus need to be added through the menu management function. If the menu is configured incorrectly, the application cannot be used normally.
* In the global routing guard, each route jumps To be judged

Option II,

Menus and routes are returned by the back end, and the
front end uniformly defines the routing components

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

The backend routing component returns the following format

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

In the rear end of the return route through addRoutesbetween dynamic mount needs to look at the data processing field component exchange for real components
if there are nested routing back-end functionality designed to add the appropriate time to pay attention to the front of the field to get the data has to be done accordingly Processing

This method also has shortcomings:
* The global routing guard has to judge every time a route jumps
* The front-end and back-end cooperation requirements are higher.

Button permissions

Solution 1:
Button permissions can also be judged by v-if,
but if there are too many pages, each page must obtain user permissions role and meata.btnPermissions in the routing table and then judge

Option II

Judge 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 authorization authentication instructions

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 instruction needs to be quoted in the button used

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

Regarding how to choose the appropriate solution for the authority, you can choose according to your own project's solution items, such as whether the routing and the menu are separated. The
authority needs to be combined between the front and the back. The front end should be controlled as much as possible, and more background judgments are required.

Guess you like

Origin blog.csdn.net/qq_38686150/article/details/112427605