1.什么是权限管理
权限是对特定资源的访问许可,权限控制就是确保用户只能访问到被分配的资源,而前端权限归根结底是请求的发起权,请求的发起可能有下面两种形式触发
1.页面加载触发(路由)
2.页面上的按钮点击触发(视图)
2.如何实现
前端权限控制可以分为四个方面
1.接口权限
2.按钮权限
3.菜单权限
4.路由权限
2.1 接口权限
接口权限一般采用jwt的形式验证,至于什么是jwt,后续文章会进行讲解,没有通过的话会返回401,跳转到登陆页面
登陆完拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部必须携带token
axios.interceptors.request.use(config => {
config.headers['token'] = cookie.get('token')
return config
})
axios.interceptors.response.use(res => { }, { response }=> {
//若token过期或发生错误
if(response.data.code === 40099 || response.data.code === 40098) {
router.push('/login')
}
})
2.2 路由权限控制
方案一
初始化即挂载全部路由,并且在路由上标记相应的权限信息,每次路由跳转前做校验,利用router.beforeEach() 这一个路由守卫对路由跳转进行拦截,判断meta中的信息
const routerMap = [
{
path: '/permission',
component: Layout,
redirect: '/permission/index',
// 一直可以被访问
alwaysShow: true,
meta: {
title: 'permission',
icon: 'lock',
// 设置谁可以访问
roles: ['admin', 'editor']
},
children: [{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'pagePermission',
meta: {
title: 'pagePermission',
roles: ['admin']
}
}, {
path: 'directive',
component: () => import('@/views/permission/directive'),
name: 'directivePermission',
meta: {
title: 'directivePermission'
}
}]
}
]
缺点
1.加载所有的路由,如果路由很多,而用户并不是所有的路由都有权限访问,对性能会有影响
2.全局路由守卫里,每次路由跳转都要做权限判断
3.菜单信息写死在前端,要改个显示文字或权限信息,需要重新编译
4.菜单和路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示。
方案二
初始化的时候挂载不需要权限控制,如登录页,404错误页。如果用户通过输入URL进行强制访问,则直接进入404,从源头上做控制
登录后,获取用户的权限信息,然后筛选有权限的路由,在全局路由守卫调用addRoutes添加路由
这里就不做代码演示了,主要是在合适的时机调用this.$route.addRoutes()这个Vue2API
缺点:
1.全局路由守卫里,每次路由跳转都要做判断
2.菜单信息写死在前端,要改个显示文字或权限信息,需要重新编译
3.菜单和路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示。
2.3 菜单权限
菜单权限可以理解成页面与路由解耦
方案一
菜单与路由分离,菜单由后端返回
定义路由
{
name: "login",
path: "/login",
component: () => import("@/pages/Login.vue")
}
全局路由守卫判断
function hasPermission (router, accessMenu) {
if (whiteList.indexOf(router.path) !== -1) {
return true
}
const menu = Util.getMenuByName(router.name, accessMenu)
if (menu.name) {
return true
}
return false
}
Router.beforeEach(async (to, from, next) => {
if (getToken()) {
const 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')
}
}
const menu = Util.getMenuByName(to.name, store.getters.accessMenu)
Util.title(menu.title)
})
Router.afterEach((to) => {
window.scrollTo(0, 0)
})
缺点
1.菜单与路由对应,前端添加新功能,需要通过菜单管理功能添加新的菜单,若菜单配置不对则会导致应用不能正常使用
2.全局路由守卫里,每次路由跳转都要做判断
方案二
菜单和路由都由后端返回
前端统一定义路由组件
const Home = () => import("../pages/Home.vue");
const UserInfo = () => import("../pages/UserInfo.vue");
export default {
home: Home,
userInfo: UserInfo
};
后端路由组件返回格式
[
{
name: 'home',
path: '/',
component: 'home'
},
{
name: 'home',
path: '/userinfo',
component: 'userInfo'
}
]
缺点
1.全后端配合要求高
2.全局路由守卫里,每次路由跳转都要做判断
2.4 按钮权限
方案一
用v-if指令判断
方案二
自定义指令