vue权限控制
1、权限的相关概念
前端权限
前端权限的控制本质上来说,就是控制前端的视图层的展示和前端所发送的强求,但是只有前端权限控制没有后端权限控制是万万不可的,前端控制权限只可以说是达到锦上添花的效果。
在login.vue中,将token放在sessionStorage中
前端权限的意义
如果说仅从能够修改服务器中数据库的数据层面上讲,确实只有后端做控制就足够了,那为什么越来越多的项目也进行了前端权限的控制,主要有这几方面的好处
- 降低非法操作的可能性
不怕贼偷就怕贼惦记,在页面中展示出一个就算点击了也最终会失败的按钮,势必会增加有心者非法操作的可能性 - 尽可能派相互不必要的请求,减轻服务器的压力
没必要的请求,操作失败的请求,不具备权限的请求,应该压根就不需要发送,请求少了,自然也会减轻服务器的压力 - 提高用户的体验
根据用户具备的权限为该用户展现自己权限范围内的内容,避免在界面上给用户带来困扰,让用户专注于分内之事
2、前端权限的控制思路
1、菜单的控制
在登录请求中,会得到权限数据,这个需要后端返回数据的支持,前端根据权限数据,展示对应的菜单,点击菜单,才能查看相关的页面
2、界面的控制
如果用户没有登录,手动在地址栏敲入管理界面的地址,则需要跳转到登录页面
如果用户已经登录,可是手动敲入非权限内的地址,则需要跳转404界面
3、按钮的控制
在某个菜单的界面中,还得根据权限数据,展示出可进行操作的按钮,比如删除,修改,增加
4、请求和响应的控制
如果用户通过非常规操作,比如通过浏览器调试工具将某些禁用按钮变成启用状态,此时发送的请求,也应该被前端所拦截
3、vue的权限控制实现
3.1菜单的控制
查看登录之后获取的数据
在返回的数据中,除了用户的基本信息外,还有两个字段很关键
token:用于前端用户的状态保持
rights:该用户具备的权限数据,一级权限就对应一级菜单,二级权限就对应二级菜单
根据rights中的数据,动态渲染左边菜单栏,数据在Login。vue中得到,但是在Home.vue中才使用,所以可以把数据用vuex进行维护
vuex中的代码
export default new Vuex.store({
state:{
rightList:[]
},
mutations:{
setRightList(state,data){
state.rightList=data
},
actions:{},
getters:{
}
}
})
Login.vue中的代码
login() {
//假设后端返回的权限数据名为res,
this.$store.commit("setRightList", res.rights);
this.$message.success('登录成功')
this.$router.push('/home')
},
Home.vue
import { mapState } from "vuex";
export default {
data() {
return {
menuList: [],
};
},
computed: {
...mapState(["rightList", "username"]),
},
created() {
//初始化menuList菜单栏的数据
//先对rightList的数据进行操作,编程menulist的样子之后
this.menuList = this.rightList;
},
刷新界面菜单会消失
原因分析
因为菜单数据是登录之后才获取到的,获取菜单数据之后,就存放在vuex中
一旦刷新界面,Vuex中的数据会重新初始化,所以会变成空的数组
因此,需要将权限数据存在sessionStorage中,所以让其和vuex中的数据保持同步
import Vue from "vue";
// import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//刷新之后这个值变为初始值了,空了,所以菜单会消失了,
// 此时未进行登录,没人调用setRightList这个方法,解决把数据存在sessionStorage中
rightList: JSON.parse(sessionStorage.getItem('rightList') || '[]'),//
},
mutations: {
setRightList(state, data) {
state.rightList = data
sessionStorage.setItem('rightList', JSON.stringfy(data))
},
},
actions: {
},
getters: {
}
})
标识用户名,方便查看当前用户
Login.vue中
login() {
//假设后端返回的权限数据名为res,
this.$store.commit("setRightList", res.rights);
this.$store.commit("setUserName", res.data.username);
},
Home.vue中
<template>
<div>{{ username }}退出</div>
<!-- <button @click="logout">退出</button> -->
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
menuList: [],
};
},
computed: {
...mapState(["rightList", "username"]),
},
created() {
//初始化menuList菜单栏的数据
//先对rightList的数据进行操作,编程menulist的样子之后
this.menuList = this.rightList;
},
methods: {
//退出登录
logout() {
this.$router.push("/login");
},
},
};
</script>>
vuex中的代码
import Vue from "vue";
// import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//刷新之后这个值变为初始值了,空了,所以菜单会消失了,
// 此时未进行登录,没人调用setRightList这个方法,解决把数据存在sessionStorage中
rightList: JSON.parse(sessionStorage.getItem('rightList') || '[]'),//将它从字符串变为一个数组
username: sessionStorage.getItem(username)//用户登录名
},
mutations: {
setRightList(state, data) {
state.rightList = data
sessionStorage.setItem('rightList', JSON.stringfy(data))
},
setUserName(state, data) {
state.username = data
sessionStorage.setItem('username',data)
}
},
actions: {
},
getters: {
}
})
退出按钮的逻辑
//退出登录
logout() {
//删除sessionStorage中的数据
sessionStorage.clear();
this.$router.push("/login");
//删除vuex中的数据 让vuex重新加载,也就是让当前的页面进行刷新
//跳转之后执行
window.location.reload();
},
我的理解:
用户登录时所做的操作首先是把菜单的数据存放在vuex中,但因为存在刷新后vuex重新加载页面会消失这种情况,所以要把这个菜单数据存放在sessionStorage中,菜单名和用户名进行同样的处理。
用户退出登陆的时候除了要跳转到登陆的页面之外,还要进行清除掉缓存中的数据和清除掉vuex中的数据,清除掉vuex中的数据,只要页面重新刷新就可以了,用的是window。loacation。reload这个方法。原因是sesstionStorage缓存被清除,所以说菜单数据和用户名的数据都为空,这样仅仅用刷新就完成了清除vuex中数据的效果。
以上 用户登录和退出 可以称为菜单的控制 也就是第一部分
3.2界面的控制
1、正常的逻辑是通过登录界面,登陆成功之后跳转到管理平台界面,但是如果用户直接敲入管理平台的地址,也是可以跳过登录的步骤,所以应该在某个时机判断用户是否登录
如何判断是否登录
sessionStorage.setItem("token", res.data.token);
什么时机
路由导航守卫
router index.js文件中
//防止用户跳过登录页面
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next()
} else {
const token = sessionStorage.getItem('token')
//判断token是否存在,如果不存在,跳转到登录页面,如果存在,则放行
if (!token) {
next('/login')
} else {
next()
}
}
})
login.vue中
login() {
//假设后端返回的权限数据名为res,
this.$store.commit("setRightList", res.rights);
this.$store.commit("setUserName", res.data.username);
sessionStorage.setItem("token", res.data.token);
//根据用户所在的权限,动态的添加路由规则
initDynamicRoutes()
},
2、虽然菜单项已经被控制住了,但是路由信息还是完整的存在于浏览器,正如zhangsan这个用户并不具备角色这个菜单,但是如果他自己在地址栏中敲入roles的地址,依然可以访问角色页面
-
路由导航守卫
路由导航守卫固然可以在每次路由地址发生变化的时候,从vuex中取出rightList判断用户将要访问的页面,这个用户到底有没有权限。不过从另一个角度来说,这个用户不具备权限的路由,是否也应该压根就不存在呢? -
动态路由
登陆成功之后动态添加 和App.vue中添加
App.vue中
import {initDynamicRoutes} from '@/router.js'
created() {
initDynamicRoutes(); //解决添加动态路由刷新时的bug
},
router index.js文件中
import store from '@/store'
//动态路由,根据条件判断动态添加
const userRule = { path: '/users', component: Users }
const roleRule = { path: '/roles', component: Roles }
const goodRule = { path: '/goods', component: GoodsList }
const categoryRule = { path: '/categories', component: GoodsCate }
//路由规则和字符串的映射关系
const ruleMapping = {
'users': userRule,
'roles': roleRule,
'goods': goodRule,
'categories': categoryRule
}
//调用这个方法有两个时机,第一个是登录成功之后被调用;
// 登录成功之后刷新的话,走的是根组件APP。vue中中的created中的initDynamicRoutes方法,来进行动态路由的添加
export function initDynamicRoutes() {
//根据二级权限,对路由规则进行动态的添加
const currentRoutes = router.options.routes
const rightList = store.state.rightList
rightList.forEach(item => {
item.children.forEach(item => {
const temp = ruleMapping[item.path]
currentRoutes[2].children.push(temp)
})
})
router.addRoutes(currentRoutes)
}
//存在bug 一旦刷新路由会重新加载的,但没有经过login.vue调用方法initDynamicRoutes,动
// 态路由也不复存在,所以在导航栏输入应有的权限也会存在加载不出俩的情况 动态路由只有在登录成功后能够添加成功
//解决方法在根组件 App.vue中
3.3按钮的控制
按钮控制
虽然用户可以看到某些页面了,但是这个页面的一些按钮,该用户可能是没有权限的。我们需要对组件中的一些按钮进行控制,用户不具备权限的按钮就隐藏或者禁用,而在这块,可以把逻辑放到自定义指令中。
router index.js
export function initDynamicRoutes() {
//根据二级权限,对路由规则进行动态的添加
const currentRoutes = router.options.routes
const rightList = store.state.rightList
rightList.forEach(item => {
item.children.forEach(item => {
const temp = ruleMapping[item.path]
temp.mata=item.rights //这里
currentRoutes[2].children.push(temp)
})
})
router.addRoutes(currentRoutes)
}
permission.js
import Vue from "vue";
import router from '@/router/index.js'
Vue.directive('permission', {
//el指当前使用的这个元素,binding可以得到指令后面的数据
inserted(el, binding) {
const action = binding.value.action
const effect=binding.value.effect
//判断,当前的路由所对应的组件中,如何判断用户是否具备action的权限
console.log(router.currentRoute.meta)//得到当前路由规则下,用户所具有的权限
//当前组件所在的路由规则,所以在添加动态路由的时候给每一个动态添加的路由规则添加一个元数据
if (router.currentRoute.meta.indexOf(action) == -1) {
//如果当前路由规则下没有这个权限,那么就将它移除掉
if(effect=='disabled'){
el.disabled=true
el.classList.add('is-disabled')
}else{
el.parentNode.removeChild(el)
}
}
}
})
user.vue
<div @click="add" v-permission="{ action: 'add',effect:'disabled' }">用户管理的按钮</div>
main.js中
import '/utils/permission.js'
3.4请求和响应的控制
请求控制
- 除了登录请求都得要带上token,这样服务器才可以鉴别你的身份
- 如果发出了非权限的请求,应该直接在前端访问内拒绝,虽然这个请求发到服务器也会被拒绝
- 响应控制
得到了服务器返回的状态码401,代表token超时或者被篡改了,此时应该强制跳转到登录页面
help.js
import axios from 'axios'
import Vue from 'vue'
import router from '../router'
const actionMapping={
'get':'view',
'post':'add',
'put':'edit',
'delete':'delete'
}
axios.interceptors.request.use((req)=>{
console.log(req.url)//user是路径
console.log(req.method) //get是请求方式
// 除了登录请求的所有请求头都应该加上请求头数据
if(req.url!=='login'){
//不是登录的请求,我们应该在请求头中,加入token数据
req.headers.Authorization=sessionStorage.getItem('token')
const action=actionMapping[req.method] //得到右边的数据,载看action位不位于当前规则的权限列表当中
//判断非权限范围内的请求
const currentRight= router.currentRoute.meta//档前路由规则的权限数据
//判断当前请求的行为 restful请求的行为 get请求 view post请求 add put请求 edit delete请求 delete
//[add view edit delete]
if(currentRight&¤tRight.indexOf(action)===-1){
//没有权限
return Promise.reject(new Error('没有权限'))
}
}
return req
})
axios.interceptors.response.use((res) => {
if (res.data.meta.status === 401) {
router.push('/login')
sessionStorage.clear()
window.location.reload()
}
return res
})
Vue.prototype.$http=axios
4.小结
前端的实现必须要后端提供数据支持,否则无法实现
返回权限数据结构,前后端需要沟通协调,怎样的数据使用起来才最方便
4.1菜单控制
- 权限的数据需要在多组件之间共享,因此采用vuex
- 防止刷新页面权限数据消失,所以要存储在sessionStorage,并且要保证两者的同步
4.2界面的控制
- 路由的导航守卫可以防止跳过登录界面
- 动态路由可以让不具备权限的界面的路由规则压根不存在
4.3按钮控制
- 路由规则中可以增加路由元数据meta
- 通过路由对象可以得到当前的路由规则及存储在此规则中的meta数据
- 自定义指令可以很方便的实现按钮控制
4.4请求和响应控制
- 请求拦截器和响应拦截器的使用
- 请求的方式约定resful
可搭配B站视频前端面试官必问系列-后台系统的权限控制与管理使用