reference documents
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 axios
for front-end and back-end interaction, and cooperates with the return of the back-end jwt
to control the authority of the interface
The user gets it after logging in token
, axios
and adds it in the request interceptor token
. If it is token
invalid or expired, it will be processed accordingly.
axios.interceptors.request.use(config => {
config.headers['token'] = cookie.get('token')
return config
})
routing authority
Option One
(meta
All 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. .
addRoutes
After 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")
}
name
The 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 name
corresponding 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 name
and the route name
are in one-to-one correspondence, and the menu returned by the backend has been filtered by the authority.
If name
the 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 addRoutes
mount 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-if
Show 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>