前言
大家好,我是啊肥,目前在尝试搭建自己的博客,做到权限这一块的时候,忽然想起似乎对这一块并没有那么熟悉,甚至对各种方案并不是很清晰,自己需要做什么,没有方向去考虑。伴着这些问题去查了资料,我也该了解了
权限管理是什么
简单来说就是对特定资源的访问许可,控制权限是确保某些用户能分配到某些资源
在前端方向中,一般去触发请求的是有页面加载触发和按钮交互触发
在这几方面入手,一般想实现几种效果:
- 用户只能看到自己有权限的组件和内容
- 用户只能访问自己有权限访问的菜单路由地址,否则跳转到提示页
- 请求拦截,请求接口时会查看权限,在前端步骤进行拦截
怎么做权限管理
接口权限
普遍流程中,会存在token,如果请求中没有token会跳转登录页重新登录,拿到token会进行存储,通过axios请求拦截器进行拦截,每次请求头部都会携带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')
}
})
按钮权限
这里都以vue来做实例
第一种:
页面通过api可获取路由里的meta,通过v-if判断
第二种
自定义vue指令判断
配置路由
{
path: '/test',
component: Test,
name: '测试',
meta: {
btnPermissions: ['admin', 'normal']
},
//页面需要的权限
children: [{
path: 'childrenTest',
component: _import('xxx'),
name: 'children测试',
meta: {
btnPermissions: ['admin']
} //页面需要的权限
}]
}
自定义指令
import Vue from 'vue'
// 权限检查方法
Vue.prototype.$_pre = function (value) {
let isExist = false;
// 获取用户按钮权限
let btnPermissionsStr = '缓存获取权限';
if (!btnPermissionsStr) return false;
if (value.indexOf(btnPermissionsStr) > -1) isExist = true;
return isExist;
};
// 指令
const pre = Vue.directive('pre', {
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.$_pre(btnPermissionsArr)) {
el.parentNode.removeChild(el);
}
}
});
export {pre}
使用
<button v-pre>点击</button>
路由权限
第一种方案
初始化挂载全部的路由,每个路由都入上文,标记权限
{
path: '/test',
component: Test,
name: '测试',
meta: {
roles: ['admin', 'normal']
},
//页面需要的权限
children: [{
path: 'childrenTest',
component: _import('xxx'),
name: 'children测试',
meta: {
roles: ['admin']
} //页面需要的权限
}]
}
缺点:
- 全局路由守卫,每次跳转都要判断
- 加载在所有的路由,如果路由较多,影响性能
- 菜单写死前端,每次更改需要发布
- 菜单跟路由耦合
第二种方案
初始化先挂载不需要权限的路由,比如登录和404错误页等等,如果用户通过url访问,会强制进入404页面,登录后获取权限,然后再去筛选对应的路由,通过addRoutes添加路由
import router from './router'
// 权限判断
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', '/xxxx']// 白名单
router.beforeEach((to, from, next) => {
if (如果有token) {
if (to.path === '/login') next({ path: '/' })
else {
if (当前用户没有拉取完user信息) {
获取用户信息.then(res => { // 拉取user
const roles = res.data.roles
根据roles生成可访问的路由fn(roles).then(() => {
router.addRoutes(可访问路由表)
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch((err) => {
next({ path: '/' })
})
} else {
// 没有动态改变权限可直接next()
if (hasPermission(可访问路由表, to.meta.roles)) next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) { // 免登录白名单,直接进入
next()
} else next('/login') // 重定向到登录页
}
})
按需挂载,在用户登录进来的时候就要知道当前用户拥有哪些路由权限
缺点:
- 全局路由守卫,每次跳转都要判断
- 菜单写死前端,每次更改需要发布
- 菜单跟路由耦合
菜单权限 菜单与路由解耦
第一种方案
菜单由后端返回,前端定义路由信息
{
name: 'xxx',
path: 'xxx',
component: () => import("xxxx/xx/xxx")
}
name字段不能为空,需要根据此字段与后端返回菜单做关联,后端返回的菜单信息中必须要有name对应的字段,并且做唯一性校验
全局路由守卫
function hasPermission(router, accessMenu) {
if (whiteList.indexOf(router.path) !== -1) return true;
let menu = '获取匹配name的路由信息'
if (menu.name) return true;
return false;
}
Router.beforeEach(async (to, from, next) => {
if (如果存在token) {
let userInfo = '获取用户信息';
if (!userInfo.name) {
try {
await 获取用户信息
await 获取菜单
if (to.path === '/login') next({ name: '首页' })
else next({ ...to, replace: true }) // 菜单权限更新完成,重新进一次当前路由
}
catch (e) {
if (whiteList.indexOf(to.path) !== -1) next() // 在免登录白名单,如上文
else next('/login')
}
} else {
if (to.path === '/login') next({ name: '首页' });
else {
if (hasPermission(to, 菜单路由)) next() // 前往对应页面
else next({ path: '/403',replace:true })
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) next() // 在免登录白名单,直接进入
else next('/login')
}
});
Router.afterEach((to) => {
window.scrollTo(0, 0);
});
每次路由跳转的时候都要判断权限,因为菜单的name与路由的name是一一对应的,而后端返回的菜单就已经是经过权限过滤的
如果路由的name找不到对应的,会到404,无权访问
缺点:
- 全局路由守卫,每次跳转都要判断
- 菜单跟路由做一一对应,如果加新功能,则两边都要添加,保证正常使用
第二种方案
菜单和路由都由后端返回
前端
const Xx1 = () => import("xxxx");
const Xx2 = () => import("xxxx");
export default {
xx1: Xx1,
xx2: Xx2
};
后端
[
{
name: "xx1",
path: "/xx1",
component: "xx1"
},
{
name: "xx2",
path: "/xx2",
component: "xx2"
}
]
在将后端返回路由通过addRoutes动态挂载之间,需要将数据处理一下,将component字段换为真正的组件
缺点:
- 全局路由守卫,每次跳转都要判断
- 更加要求前后端的配合