您现在的位置是:首页 >技术杂谈 >vue权限管理的设计与实现网站首页技术杂谈
vue权限管理的设计与实现
一.什么是权限管理
在web应用中权限管理,一般指根据系统设置和分配给某个角色的应用权限,用户可以访问而且只能访问自己被分配的资源。权限管理几乎出现在任何系统里面,在web后台管理系统里面尤为常见。
二.权限管理的分类
-
后端权限
权限管理主要还是围绕着数据进行的,核心还是服务器中数据的变化,所以后端一般才是权限的关键,后端告诉前端改用户拥有什么权限,然后前端在进行分配给用户,因此在很长的一段时间内,权限一直都只是后端程序要考虑的话题。但是随看前后端分离开发模式的流行,越来越多的项目也在前端进权限控制。
-
前端权限
前端权限的控制从本质上来说,就是控制前端的页面的展示和前端所发送的请求。但是只有前端权限控制必要要依靠后端的支持才能进行。前端权限控制只可以说是达到锦上添花的效果,可以优化界面逻辑,简化项目复杂度,提升项目的运行效率,减轻服务器的压力等等,所以,权限控制在前端中也比较重要的知识点了。
三.前端权限的意义
-
降低用户非法操作的可能性
-
提高用户体验,无权限的菜单按钮不在展示
-
拦截不必要的请求,减轻服务器的压力
四.前端权限管理的实现思路
菜单控制:在登录请求成功之,会得到权限数据当然,这个需要和后端商量返回数据的格式,前端根据权限数据,展示对应的菜单点击菜单,才能查看相关的界面。
界面控制:界面控制有两种,第一种就是用户没有登录前,在地址栏中输入项目中非登录页的项目地址,这时应该将其访问拦截重定向到登录页。 第二种就是针对不同的用户,有些特定用户所拥有的特定页面就不应该呈现给用户,即使他非法敲入看到的地址也不行,输入非法地址,应该给他重定向到404页面。
按钮控制:不同的用户对按钮的操作权限不同,第一种用户只能查看数据,不能更改数据,有的用户则拥有对数据增删改查的功能,所以同一个按钮当用户没有权限的时候我们应该给他隐藏或者禁用。第二种则是,一个页面存在多个tabs标签页面,我们也应该根据权限的不同做不同的展示。
请求和响应的控制:
对于超出用户权限以外的请求和响应对系统来说都是不必要的,会造成不必要的服务器开销和时间成本,这种请求和响应都是需要控制的,让其根本无法发送,比如一个编辑按钮,由于没有权限,在页面上是把当前按钮禁用了的,但是如果用户打开控制台,强行将此按钮的disabled属性置为true,这个时候没有权限的用户还是可以操作这个按钮,虽然可能后台最终会做拦截但是对用户来说体验不是很好,所以我们对当前发出的非法请求还是需要做出拦截。
五.动态路由(菜单相关)
动态路由介绍:动态即不是写死的,是可变的。我们可以根据存在的权限加载对应的路由,没有权限的路由我们就不加载,避免造成资源浪费,也算对项目的一个优化点了,动态路由的使用一般结合角色权限控制一起使用。
动态路由的优点:
-
安全性,当用户手动输入没有权限的地址进入某一个页面的时候,会自动重定向到404,无需我们单独在路由守卫里面进行控制
-
灵活,可以配置菜单的增加、减少,这样不用每次修改再去处理。后续的菜单增加,路由统一处理,方便快捷。
六.功能实现
这里我将使用一个demo进行举例,demo里面会对菜单,界面,按钮,请求四个方面进行实现,demo技术栈使用vue2+vue-router+element-ui来实现,后台数据使用mock.js模拟实现。
方法1(不使用动态路由)
这里整体实现思路是给每个menu菜单,按钮都分配一个独有的code,然后后台返回对应角色用户的code权限表集合,前端拿到code权限表在做筛选处理
demo里面存在两个用户角色,一个是admin(超级管理员),拥有所有的权限,还有一个是test(普通管理员),拥有一部分的权限
用户登录之后服务端返回一个数据,这个数据有角色对应的权限code表(permission)和token
我们在登录成功之后拿到数据先存储在vuex中,之后我们在进行使用,但是我们会发现一个问题我们刷新一下页面之后vuex的数据会消失
原因:vuex里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里面的数据就会被重新赋值为初始值。
解决方式:将数据保存在localstorage,sessionstorage或cookie中(因为这些是存储在浏览器的,相当于存储在硬盘中,如果不主动清除,会一直存在),并且让它和vuex保持同步。
- 菜单控制:根据返回的code表去与router对象进行对比,每个router对象都有一个roleCode属性,代表当前的menu菜单
这里我们使用了一个方法来进行筛选,该方法传入当前code表与所有的router对象,最后会返回符合code权限的全新路由对象,左侧的menu初始化是整个router对象,也就是具有所有menu菜单,现在经过筛选之后,只返回了当前角色具有权限的menu菜单
登录成功按照上面的方法筛选得到以下菜单
这样之后菜单模块看起来好像就是大功告成了,但其实如果我们重新刷新的话会发现刚刚筛选好的menu菜单就会复原成所有菜单,筛选菜单方法失效了。
原因:路由筛选路由是在登录成功之后才会调用的,刷新的时候并没有调用,所以刷新之后路由没有添加上。
解决方式:可以在app.vue中的created中调用添加筛选菜单路由的方法
- 界面控制:由于没有使用动态路由的方式,在项目里面实际上所有的路由都是进行了注册的,所以哪怕在页面上没有显示对应的menu菜单,但是我们在地址栏上输入不存在该权限的地址我们还是可以进入到该页面,所以我们接下来应该对界面进行权限控制
admin用户
test用户
test用户是不存在角色管理整个模块的
所以我们这里要多加一个步骤,就是在路由前置守卫里面进行拦截,如果判断没有该权限应该重定向到404页面。
解决方式:在路由前置守卫里面给他进行旁段,如果有权限放行,无权限直接404
问题:我想着这样就可以了,但是问题又来了,我尝试访问test用户不存在的角色模块时候,在地址栏上输入地址,结果却陷入了死循环。
于是我查阅资料发现这个问题其实也是对 vue-router 的router.beforEach运行机制不了解导致
解决方式:基于整个机制,我们对它进行改造一下
友情链接:vue-router死循环这个问题可以参考这个博客https://blog.csdn.net/weixin_45306532/article/details/114434748
上述操作完成之后,菜单部分就真的真的真的完工大吉了,输入非法地址,进入404
- 按钮权限:现在用户可以看到某些具有权限的界面了,但是这个界面的一些按钮和tabs页该用户可能是没有权限的。
- 按钮处理:用户不具备权限的按钮就隐藏或者禁用,而在这块的实现中,可以把该逻辑放到自定义指令中
a:新建permission.js文件,并且注册自定义指令
b.main.js引入permission.js
c.按钮上面加上自定义指令,并且把当前按钮对应的code传入自定义指令中
2.tabs处理:在当前页面组件的computed里面进行tabs过滤
a.tabs的配置数据里面有一个role属性,对应当前权限
b.用户管理/用户等级组件的computed进行处理
上述操作完成之后,test用户的效果就是如下
test用户
admin用户
- 接口请求控制:接口权限目前一般采用jwt的形式来验证,没有通过的话一般返回401,跳转到登录页面重新进行登录,登录完拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token。
到这里按钮级别的控制就结束了 。
方法2(使用动态路由)
此方法在第一种方法的菜单权限基础上加入了动态路由
我的router里面只保留了首页/home,登录页/login,以及404页面,因为这几个页面什么用户登录都是有权限查看的。
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/views/login/index.vue'
import Layout from '@/components/Layout.vue'
import NotFound from '@/components/NotFound.vue'
// isShow属性:el-menu树要不要渲染
// isMenuItem:el-menu树有没有字节点
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
redirect: '/home',
isShow: true,
authName: '首页',
isMenuItem: true,
component: Layout,
meta: {
roleCode: 'home'
},
children: [
{
id: 110,
meta: {
roleCode: ''
},
authName: '项目权限介绍',
path: '/home',
component: (resolve) => require(['@/views/home'], resolve)
}
]
},
{
path: '/login',
component: Login,
isShow: false
},
{
isShow: false,
path: '*',
component: NotFound
},
{
isShow: false,
path: '/404',
component: NotFound
}
]
})
// 筛选有权限的菜单
export function filterRoute(arr, arr1) {
const realRoutes = []
arr.forEach((v) => {
// console.log(v, 'vvv')
if (!v.meta.roleCode) {
realRoutes.push(v)
}
arr1.forEach(item => {
if (item === v.meta.roleCode) {
if (v.children && v.children.length > 0) {
v.children = filterRoute(v.children, arr1)
}
realRoutes.push(v)
}
})
})
return realRoutes
};
router.beforeEach((to, from, next) => {
console.log(router.options.routes, 'over111')
if (to.path === '/login') {
next()
} else {
const token = sessionStorage.getItem('token')
if (!token) {
next('/login')
} else {
// 筛选权限
next()
}
}
})
export default router
另外所有的router我给他放在了另外一个js文件中,用于权限的判断,有权限的再给他动态注册进入到router路由对象中去
router/routingArray.js
import Layout from '@/components/Layout.vue'
const router = [
{
id: 125,
authName: '用户管理',
path: '/User',
name: 'User',
redirect: '/User/Userlist',
icon: 'icon-user',
meta: {
roleCode: 'user'
},
isMenuItem: false,
isShow: true,
component: Layout,
children: [
{
id: 110,
isMenuItem: true,
meta: {
roleCode: 'userlist'
},
authName: '用户列表',
path: '/User/Userlist',
name: 'Userlist',
component: (resolve) => require(['@/views/user/userlist.vue'], resolve)
},
{
id: 120,
isMenuItem: true,
meta: {
roleCode: 'usergrade'
},
authName: '用户等级',
path: '/User/Usergrade',
component: (resolve) => require(['@/views/user/usergrade.vue'], resolve)
}
]
},
{
isShow: true,
isMenuItem: false,
id: 103,
path: '/Roles',
name: 'Roles',
meta: {
roleCode: 'roles'
},
authName: '角色管理',
redirect: '/Roleslist',
icon: 'icon-tijikongjian',
component: Layout,
children: [
{
id: 111,
isShow: true,
isMenuItem: true,
meta: {
roleCode: 'roleslist'
},
authName: '角色列表',
path: '/Roleslist',
component: (resolve) => require(['@/views/role/rolelist.vue'], resolve)
}
]
}
]
export default router
在登录成功之后做处理
注意点:在vue-router4.x版本之后 router.addRoutes已经废弃,官方已经改为outer.addRoute,动态注入多个需要循环依次注入。
刷新之后和方法一一样,在app.vue中的created中调用添加筛选菜单路由的方法,避免刷新失效
第二种方法的介绍就到这里了,页面,按钮,请求和响应的权限都与方法一相同。
七:模板练习
demo模板已经上次至码云,需要的小伙伴可以自行获取,制作不易,麻烦点一个免费的star,抱拳抱拳,如果不足,欢迎各位大佬补充。