Use global route guard

implementation

The front end defines the route and marks the corresponding permission information on the route

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: (a)= > import('@/views/permission/page'),
      name: 'pagePermission'.meta: {
        title: 'pagePermission'.roles: ['admin'] // or you can only set roles in sub nav}}, {path: 'directive'.component: (a)= > import('@/views/permission/directive'),
      name: 'directivePermission'.meta: {
        title: 'directivePermission'
        // if do not set roles, means: this page does not require permission}}}]]Copy the code

The global routing guard determines whether the user is logged in each time and jumps to the login page if not. If you have logged in to the system and obtained the user permission information (such as roles) returned from the background, determine whether the user has the permission to access the current route. Locate the route based on the route name and determine whether the user has the permission information specified in the route (for example, roles: [‘ admin ‘, ‘editor’])). Without permissions, jump to the predefined interface (403,404, etc.).

In this way, menus can be directly generated by routes (menus that users do not have permissions will also be displayed, and permissions will be judged only when they click the jump), or routes can be filtered according to user permissions after user login to generate menus (menus must be saved in VUEX).

Iview-admin is still used this way

disadvantages

  • All routes are loaded. If a large number of routes are not accessible by users, performance may be affected.

  • In the global route guard, permissions are checked every time a route jumps.

  • Menu information written dead in the front, to change a display text or permission information, need to recompile

  • The menu is coupled with the route. When defining the route, there is also the addition of the menu to display the title, icon and other information, and the route is not necessarily displayed as a menu, but also add more fields for identification

The login page is separated from the main application

To address the disadvantages of the former implementation, you can place the login page on a different page (not in the same VUE application instance) from the main application.

implementation

, after a successful login page jump (real page jump, not routing jump), and applies user rights transfer to the main page, before the main application is initialized, routing, according to the user permission to screen screen after routing as vue instantiate the parameters, rather than the former all routing passed in a way, You don’t need to do permission checks in the global routing guard.

disadvantages

  • Need to do page jumps, not pure one page application
  • Menu information written dead in the front, to change a display text or permission information, need to recompile
  • The menu is coupled with the route. When defining the route, there is also the addition of the menu to display the title, icon and other information, and the route is not necessarily displayed as a menu, but also add more fields for identification

useaddRoutesDynamically mount routes

AddRoutes allows dynamic routing to be mounted after application initialization. With this new pose, there is no need to filter routes before application initialization as in the previous approach.

implementation

During application initialization, first mount routes that do not require permission control, such as login pages, 404 error pages, etc.

The question is, when and where should addRoutes be called

After login, obtain the user’s permission information, filter the routes that have permission to access, and then call addRoutes to addRoutes. This method works. But you don’t need to log in every time you enter an app, and you need to log in again when you refresh your browser.

So addRoutes is still called 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

// 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) { // Check whether the current user has finished fetching user_info
        store.dispatch('GetUserInfo').then(res= > { / / pull user_info
          const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']
          store.dispatch('GenerateRoutes', { roles }).then((a)= > { // Generate an accessible routing table based on roles permissions
            router.addRoutes(store.getters.addRouters) // Dynamically add the accessible routing tablenext({ ... to,replace: true }) // the hack method ensures that addRoutes is complete,set the replace: true so the navigation will not leave a history record
          })
        }).catch((err) = > {
          store.dispatch('FedLogOut').then((a)= > {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/'})})})}else {
        // If there is no need to dynamically change permissions, you can directly next() delete the lower permissions ↓
        if (hasPermission(store.getters.roles, to.meta.roles)) {
          next()//
        } else {
          next({ path: '/ 401'.replace: true.query: { noGoBack: true}})}/ / to write them}}}else {
    /* has no token*/
    if(whiteList.indexOf(to.path) ! = =- 1) { // Enter the whitelist directly
      next()
    } else {
      next('/login') // Otherwise all redirects to the login page
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }
})

router.afterEach((a)= > {
  NProgress.done() // finish progress bar
})
Copy the code

The key code is as follows

if (store.getters.roles.length === 0) { // Check whether the current user has finished fetching user_info
        store.dispatch('GetUserInfo').then(res= > { / / pull user_info
          const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']
          store.dispatch('GenerateRoutes', { roles }).then((a)= > { // Generate an accessible routing table based on roles permissions
            router.addRoutes(store.getters.addRouters) // Dynamically add the accessible routing tablenext({ ... to,replace: true }) // the hack method ensures that addRoutes is complete,set the replace: true so the navigation will not leave a history record
          })
        }).catch((err) = > {
          store.dispatch('FedLogOut').then((a)= > {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/'})})})Copy the code

The above code is an implementation of vue-element-admin

disadvantages

  • In the global route guard, every route jump is judged
  • Menu information written dead in the front, to change a display text or permission information, need to recompile
  • The menu is coupled with the route. When defining the route, there is also the addition of the menu to display the title, icon and other information, and the route is not necessarily displayed as a menu, but also add more fields for identification

Menus are separated from routes and returned by the back end

Menu display title, pictures and other need to change at any time, to do the menu management function.

The back end returns directly to accessible menus based on user permissions.

implementation

The front end defines the routing information (standard route definition, no additional marker fields are required).

{
    name: "login".path: "/login".component: (a)= > import("@/pages/Login.vue")}Copy the code

The name field is not empty. You need to associate this field with the back end return menu.

When doing the menu management function, there must be a field corresponding to the name field of the front-end route (it can also be other fields, as long as the menu can find the corresponding route or route can find the corresponding menu), and do the uniqueness check. The menu also needs to define permission fields, which can be one or more. Other information, such as display titles, ICONS, sorting, locking and so on, can be designed according to actual needs.

We’re still 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 })// The menu permission update is complete, re-enter the current route}}catch (e) {
        if(whiteList.indexOf(to.path) ! = =- 1) { // Enter the whitelist directly
          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) { // Enter the whitelist directly
      next()
    } else {
      next('/login')}}let menu = Util.getMenuByName(to.name, store.getters.accessMenu);
  Util.title(menu.title);
});

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

Copy the code

The above code is an implementation of vue-Quasar-admin. Every time because there is no use addRoutes, routing jump to determine its jurisdiction, the judgment is also very simple, because the name of the menu name is one-to-one with routing, then the return of the menu already was filtered through the permissions, so if according to the route name can not find the corresponding menu, the user has no permission to access.

If there are many routes, only the routes that do not require permission control can be mounted during application initialization. After obtaining the menu returned by the back end, according to the mapping between the menu and the route, the accessible route is screened out and dynamically mounted through addRoutes.

disadvantages

  • Menus need to map routes one by one. New functions are added in the front end, and new menus need to be added through the menu management function. If the menus are incorrectly configured, applications cannot work properly
  • In the global route guard, every route jump is judged

Menus and routes are returned entirely from the back end

Menus returned from the back end work, but what about routes returned from the back end? Take a look at the definition of a route

{
    name: "login".path: "/login".component: (a)= > import("@/pages/Login.vue")}Copy the code

If the back end returns directly

{
    "name": "login"."path": "/login"."component": "() => import('@/pages/Login.vue')"
}
Copy the code

What the hell is this? Obviously not. () => import(‘@/pages/ login.vue ‘) webpack will not compile login.vue if this code does not appear in the front end

implementation

The front-end defines routing components uniformly, for example

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

The routing component is defined as this key-value structure.

Backend return format

[{name: "home".path: "/".component: "home"
      },
      {
        name: "home".path: "/userinfo".component: "userInfo"}]Copy the code

Between dynamically mounting the back-end return route through addRoutes, the data needs to be processed by changing the Component field to a real component.

Whether menus and routes need to be separated and how to correspond can be handled according to actual requirements.

If there are nested routines, add corresponding fields when designing back-end functions. The front end should also do the corresponding processing to get the data.

disadvantages

  • In the global route guard, every route jump is judged
  • The front and rear ends require more coordination

Do not use global route guard

In the previous methods, in addition to the separation of the login page from the main application, each route hop is judged in the global route guard.

implementation

During application initialization, only routes that do not require permission control are mounted

const constRouterMap = [
  {
    name: "login".path: "/login".component: (a)= > import("@/pages/Login.vue")}, {path: "/ 404".component: (a)= > import("@/pages/Page404.vue")}, {path: "/init".component: (a)= > import("@/pages/Init.vue")}, {path: "*".redirect: "/ 404"}];export default constRouterMap;
Copy the code
import Vue from "vue";
import Router from "vue-router";
import ConstantRouterMap from "./routers";

Vue.use(Router);

export default new Router({
  // mode: 'history', // require service support
  scrollBehavior: (a)= > ({ y: 0 }),
  routes: ConstantRouterMap
});
Copy the code

After successful login, jump to/route

submitForm(formName) {
      let _this=this;
      this.$refs[formName].validate(valid= > {
        if (valid) {
          _this.$store.dispatch("loginByUserName", {name:_this.ruleForm2.name,
            pass:_this.ruleForm2.pass
          }).then((a)= >{
            _this.$router.push({
              path:'/'})})}else {
          
          return false; }}); }Copy the code

Because there is currently no/route, it will jump to /404

<template> <h1>404</h1> </template> <script> export default { name:'page404', mounted(){ if(! this.$store.state.isLogin){ this.$router.replace({ path: '/login' }); return; } if(! this.$store.state.initedApp){ this.$router.replace({ path: '/init' }); return } } } </script>Copy the code

The 404 component determines whether it has logged in, and then whether the application has been initialized (whether user permissions, access menus, routes, etc., have been retrieved from the back end). Jump to the /init route if there is no initialization

<template> <div></div> </template> <script> import { getAccessMenuList } from ".. /mock/menus"; import components from ".. /router/routerComponents.js"; export default { async mounted() { if (! this.$store.state.isLogin) { this.$router.push({ path: "/login" }); return; } if (! This.$store.state.initedApp) {const loading = this.$loading({lock: true, text: "initialization ", spinner: "El - icon - loading", the background: "rgba (0, 0, 0, 0.7)"}); let menus = await getAccessMenuList(); // Simulate getting var menus from the back end. for (let router of routers) { let component = components[router.component]; router.component = component; } this.$router.addRoutes(routers); this.$store.dispatch("setAccessMenuList", menus).then(() => { loading.close(); this.$router.replace({ path: "/" }); }); return; } else { this.$router.replace({ path: "/" }); }}}; </script>Copy the code

Init component to determine whether the application is already initialized (avoid entering the current component directly from the address bar after initialization).

Redirect/route if initialized (redirect 404 if no secondary route is defined in the route returned from the back end).

Without initialization, call the remote interface to get the menu, route, etc., process the route returned by the back end, assign Component to the real component, then call addRoutes to mount the new route, and finally jump/route. Menu processing is also here, see actual requirements.

Implementation example

disadvantages

  • I made a judgment on 404 page and felt weird
  • An additional init page component was introduced

conclusion

The latter two implementations are recommended.