One, foreword

In the AD machine project, the permission management of roles is a difficult problem that has been stuck for a long time. First of all, the permission control we determined is divided into two parts, which are divided into finer parts according to the size of the grain:

  • Interface access permission control
  • Page permission control
    • Whether the page in the menu can be accessed
    • Whether the permission control for buttons (add, Delete, change) on the page is displayed

Let’s take a look at how these permission controls are implemented.

2. Interface access permission control

Interface permissions are used to verify users. Normally, the server needs to return a Token to the foreground when the user logs in, and then the foreground needs to bring this Token with it every time the interface is called in the future.

The server obtains the Token and compares it with the Token. If the Token passes, the server can access it.

The existing method is to store the Token returned by the background directly to the sessionStorage in the successful login callback. However, when the request is made, the Token is fetched and put into the headers and passed to the background. The code is as follows:

this.$http({
          method: 'get',
          url: 'test/query? id=20',
          withCredentials: true,
          headers: {
            token: sessionStorage.getItem('token'),
            name: sessionStorage.getItem('name'}}). Then (response => {// Request after successful operation})Copy the code

Later found in some articles axios can directly in the interceptor will be Token into config. The headers. Authorization, as global incoming. Here is the code:

//main.js
import axios from 'axios'Const service = axios.create({timeout: 5000}) // baseURL // axios.defaults.baseurl ='https://api.github.com'; HTTP request blocker / / / / every request for increase Authorization HTTP header fields, the content of token service. The interceptors. Request. Use (config = > {if (store.state.user.token) {
            config.headers.Authorization = `token ${store.state.user.token}`;
        }
        return config
    },
    err => {
        return Promise.reject(err)
    }
);
export default service
Copy the code

Third, page permission control

As mentioned earlier, there are two types of page permission control:

  • Whether the page in the menu can be accessed

  • Whether the permission control for buttons (add, Delete, change) on the page is displayed

These permissions are generally configured on a fixed page and saved in the database.


Button permissions aside, page access permissions can be implemented in two ways:

  • All menus are displayed. When users access menus that are not within their permissions, a message is displayed indicating that the permissions are insufficient
  • Only the menu within the permission that the current user can access is displayed. If the user forcibly accesses the menu through the URL, 404 will be directly entered

Since you can’t point after showing it, how many meanings is that? Are you kidding me? The so-called out of sight is out of mind. After comprehensive consideration, it must be scheme 2 that is more in line with good user experience.

Ok, now let’s comb through the general page access flow:

After the completion of the process sorting, we began to write in detail.

1. Create a routing table

Creating a routing table is not really difficult, just follow the example given in the vue-Router documentation. But because some pages don’t require access,

So you need to write login, 404, maintenance, etc. pages to the default route, and other pages that need permissions to a variable or a file, which can be

To effectively reduce the subsequent maintenance pressure.

The code for index.js will be posted below, and the asynchronous route will be reduced to avoid taking up too much space.

// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import App from '@/App'
import store from '.. /store/index'Vue.use(Router); // Manually jump page whiteList const whiteList = ['/']; // Default page without permission const constantRouterMap = [{path:'/',
    name: 'login',
    component: (resolve) => require(['@/components/login'], resolve)
  },
  {
    path: '/index',
    name: 'nav.Home',
    component: (resolve) => require(['@/components/index'], resolve)
  },
  {
    path: '/templateMake',
    name: 'Template making',
    component: (resolve) => require(['@/components/Template/templateMake'], resolve)
  },
  {
    path: '/programMack',
    name: 'Programme making',
    component: (resolve) => require(['@/components/Template/programMack'], resolve)
  },
  {
    path: '/release',
    name: 'Program Release',
    component: (resolve) => require(['@/components/Program/release'Resolve)}] // Register a routeexportconst router = new Router({ routes: constantRouterMap }); // Asynchronous routing (pages requiring permissions)export const asyncRouterMap = [

  {
    path: '/resource',
    name: 'nav.Resource',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Resource/resource'], resolve)
  },
  {
    path: '/template',
    name: 'nav.Template',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Template/template'], resolve)
  },
  {
    path: '/generalSet',
    name: 'nav.System',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/SystemSet/generalSet'], resolve)
  },
  {
    path: ' ',
    name: 'nav.Log',
    component: App,
    children: [
      {
        path: '/userLog',
        name: 'nav.UserLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/userLog'], resolve),
      },
      {
        path: '/operatingLog',
        name: 'nav.SystemLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/operatingLog'], resolve),
      },
    ]
  }
  ]
];
Copy the code

Matters needing attention: One important thing to note here is that 404 pages must be loaded last. If a 404 is declared with a constantRouterMap, all subsequent pages will be blocked to 404. See addRoutes when You’ve got a wildcard Route for 404s does not work for more details

2. Page access rights

We started with a rough page access flow. Let’s implement the core part first:

First we get the user permission list. Here we will get into vuEX status management. The official documentation explains it in detail, but this is not enough.

// store/index.js
import Axios from 'axios'
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);
const axios = Axios.create();

const state = {
  mode: 'login',
  list: []
};

const getters = {};

const mutations = {
  setMode: (state, data) => {
    state.mode = data
  },
  setList: (state, data) => { state.list = data } }; Const Actions = {// getPermission({commit}) {return new Promise((resolve, reject) => {
      axios({
        url: '/privilege/queryPrivilege? id=' + sessionStorage.getItem('privId'),
        methods: 'get',
        headers: {
          token: sessionStorage.getItem('token'),
          name: sessionStorage.getItem('name'}}). Then ((res) => {'setList', res.data.cust.privileges[0].children);
        resolve(res.data.cust.privileges[0].children)
      }).catch(() => {
        reject()
      })
    })
  }
};

export default new Vuex.Store({
  state,
  mutations,
  actions,
  getters
})
Copy the code

Ok, now we request the background to get the permission data and store the data in VUEX. Next, we need to use the asynchronous routing table written before the returned data is matched, and combine the matching result with the static routing table to open the final actual routing table.

One of the most important is the addRoutes method, which has been added to vue-Router2.2.0.

Router.addroutes (Routes) 2.2.0+ Dynamically added more routing rules. The argument must be an array that matches the Routes option.

Now we can start using addRoutes for route matching. Here’s the code:

// router/index.js /** * Matches routes by permission * @param {array} permission Permission list (menu list) * @param {array} asyncRouter Asynchronous route object */function routerMatch(permission, asyncRouter) {
  returnnew Promise((resolve) => { const routers = []; // Create a routefunctionPermission. ForEach ((item) => {// Add the router object to the routers.if (item.children && item.children.length) {
          createRouter(item.children)
        }
        letpath = item.path; Asyncrouter. find((s) => {// Loop asynchronous routes and add the routes matching the permission list to the routers.if (s.path === ' ') {
            s.children.find((y) => {
              if(y.path === path) { y.meta.permission = item.permission; routers.push(s); }})}if (s.path === path) {
            s.meta.permission = item.permission;
            routers.push(s);
          }
        })
      })
    }

    createRouter(permission)
    resolve([routers])
  })
}
Copy the code

Then we write the navigation hooks

// router/index.js
router.beforeEach((to, form, next) => {
  if (sessionStorage.getItem('token')) {
    if (to.path === '/') {
      router.replace('/index')}else {
      console.log(store.state.list.length);
      if(store.state.list.length === 0) {// If there is no permission list, a new request will be made to the background store.dispatch('getPermission'Then (res => {// Call routerMatch(res, AsyncRouterMap). Then (res => {router. AddRoutes (res[0]); next({ ... to, replace:true })
          })
        }).catch(() => {
          router.replace('/')})}else {
        if (to.matched.length) {
          next()
        } else {
          router.replace('/')}}}}else {
    if (whiteList.indexOf(to.path) >= 0) {
      next()
    } else {
      router.replace('/')}}});Copy the code

Now that we’ve completed the permission control for page access, let’s look at the permission part of the operation button.

4. Data operation rights

Do you still remember the extra code in the previous routing configuration, let’s take a look at it:

// Asynchronous routing (pages requiring permissions)export const asyncRouterMap = [

  {
    path: '/resource',
    name: 'nav.Resource',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Resource/resource'], resolve)
  },
  {
    path: '/template',
    name: 'nav.Template',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Template/template'], resolve)
  },
  {
    path: '/generalSet',
    name: 'nav.System',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/SystemSet/generalSet'], resolve)
  },
  {
    path: ' ',
    name: 'nav.Log',
    component: App,
    children: [
      {
        path: '/userLog',
        name: 'nav.UserLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/userLog'], resolve),
      },
      {
        path: '/operatingLog',
        name: 'nav.SystemLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/operatingLog'], resolve),
      },
    ]
  }
  ]
];
Copy the code

Add meta fields for each routing page. Assign the matched detailed permission fields here in the routerMatch function. This gives you this field in the Route object on each page.

asyncRouter.find((s) => {
          if (s.path === ' ') {
            s.children.find((y) => {
              if(y.path === path) {// Assign y.meta.permission = item.permission; routers.push(s); }})}if(s.path === path) { s.meta.permission = item.permission; routers.push(s); }})Copy the code

Next, we write a vUE custom directive to determine which elements of the page need to be authenticated, such as something like this:

<a @click="upload" v-allow="' 3 '"></a> /* 3 represents the ID of an upload permission, and three of the permissions display buttons */Copy the code

We directly register a global directive that uses vNode to access vUE’s methods. The code is as follows:

//main.js // button permission directive vue.directive ('allow', {
  inserted: (el, binding, vnode) => {
    let permissionList = vnode.context.$route.meta.permission;
    if(! permissionList.includes(binding.value)) { el.parentNode.removeChild(el) } } })Copy the code

At this point, the permission control flow is complete, so let’s take a look at the complete permission control flow diagram at the end.

Complete flow chart of routing control

Vi. References

  1. Vue + ElementUI Manual background management site access control

  2. Hand touch hand, take you to use vUE lui background authority control