权限相关的概念
某个用户是否有对某个数据的增删改查等操作的权限
后端权限(对数据库中的数据的控制)
1.权限的核心在于服务器中数据的变化,后端权限可以控制某个用户是否可以对数据进行增删改查的操作
后端如何知道请求是哪个用户发送的??
状态保持:
cookie,session,token
一般而言前后端分离开发采用token
来鉴权
2.后端权限设计RBAC(基于角色的权限控制)
用户==>角色==>权限
前端权限(视觉层面的控制)
主要用来控制前端视图层的展示与前端所发送的请求
意义:
- 降低非法操作的可能性
- 排除不必要的请求,减轻服务器的压力
- 提高用户的体验
前端权限控制的思路:(不限制某一技术,是个解决方案)
1.菜单的控制(后台管理系统中侧边栏的展示)
在登陆请求中,会得到权限的数据,需要后端来返回数据作为支持,前端根据后端返回的数据展示对应的菜单,点击菜单,查看对应的界面
2.界面的控制
如果用户没有登陆,手动在地址栏输入管理界面的地址,需要跳转到登陆界面
如果用户已经登陆,手动输入会权限内的地址,则需要跳转到404界面
3.按钮的控制
在某个菜单的界面中,需要根据权限数据展示可操作的按钮
4.请求与响应的控制
如果用户通过非常规的操作,如通过浏览器的调试工具将禁用的按钮变为可用,发送请求,也应该被拦截,尽管后端会返回错误的状态码,目的是减轻服务器的请求次数
Vue权限控制的实现:
1.菜单的控制
前提:登陆成功后拿到的对应的数据
{
"data":{
"email": "[email protected]",
"id": 500,
"mobile": "13999999999",
"rid": 0,
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1MTI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHm-tPsO9r_pxHIQ5i5L1kX9RX444uwnRGaIM",
"username": "admin"
},
"meta":{
"msg": "登录成功",
"status": 200
},
"right":[
{
"authName": "事件管理",
"children":[
{
"authName": "事件添加",
"id": 111,
"path": "eventAdd",
"rights":["view","edit","add","delete"]
},
],
"icon": "icon-user",
"id": 110
},
{
"authName": "系统管理",
"children": [
{
"authName": "角色管理",
"id": 172,
"path": "role",
"rights":["view","edit","add","delete"]
},
],
"icon": "icon-shangpin",
"id": 170,
},
]
}
- token用于前端页面的用户状态的保持
- right数组,是该用户具备的权限数据,一级权限对应一级菜单,二级权限对应二级菜单
注意:这些数据是在登陆界面获取到,如果想在首页使用,需要用到vuex来做全局的状态管理
vuex代码:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//引入并使用vux,抛出vuex new的对象
export default new Vuex.Store({
state: {
rightList: JSON.parse(sessionStorage.getItem('rightList') || '[]'),
username: sessionStorage.getItem('username')
//存储的值位于本地存储中,在这里读取出来存储在state中,页面中也可能用到当前用户的信息,需要的话进行存储,这里只用到了用户名
//此处需要注如果没有对state中的值进行本地的存储会出现一个bug:页面刷新,vuex中的数据初始化,会变为空,只需要将数据存储在本地存储中,使其与vuex中的数据保持同步即可解决
},
//state存储值,不建议在这里面进行更改,要更改使用mutations来定义方法进行更改
mutations: {
setRightList(state, data) {
state.rightList = data
sessionStorage.setItem('rightList', JSON.stringify(data))
},
setUsername(state, data) {
state.username = data
sessionStorage.setItem('username', data)
}
},
actions: {
},
modules: {
}
})
login代码:
//登陆成功后的回调中进行返回信息的存储
this.$store.commit("setRightList", res.right);
this.$store.commit("setUsername", res.data.username);
sessionStorage.setItem("token", res.data.token);
//token就不需要存储在vuex中,只需要存储在本地即可,在页面需要退出登陆时清空本地存储的值,进行路由的跳转并刷新页面即可将本地存储与vuex中存储的值清空
index代码:
import { mapState } from "vuex";
//引入mapState函数并对vuex中的数据做一个映射
created() {
this.menulist = this.rightList;
this.msg=this.username
//映射出来的值直接赋值使用即可
},
computed: {
...mapState(['rightList', 'username']),
},
//退出登陆清空vuex与本地存储的数据,在退出登陆的方法中写入一下即可:
logout() {
sessionStorage.clear();
setTimeout(() => {
this.$router.push("/login");
this.$message.success("退出登陆成功");
}, 1000);
//设置延迟执行函数为了用户体验
},
2.界面的控制
- 未登录直接访问
如何判断用户是否登陆?(token)
前面已经进行token的存储
什么时机进行判断?(路由导航守卫)
router.js代码:
router.beforeEach((to, from, next) => {
if (to.path === '/login'||to.path==='/register') {
//如果是跳转到登陆或者注册是不用进行判断
next()
} else {
const token = sessionStorage.getItem('token')
if(!token) {
//跳转除登陆注册组件时进行token的判断,如果不存在跳转登陆页面进行登陆
next('/login')
} else {
next()
}
}
})
- 用户已经登陆但是在地址栏输入不具备权限的页面仍然可以访问!
解决:
(1).使用路由导航守卫
固然是可以的,可以在每次地址栏变化时从vuex中取出right进行判断用户将要访问的界面这个用户是否有权限进行访问。
如果用户不具备权限的路由是否应该是不存在的??
(2).动态路由的使用
router.js代码:
import Users from '@/components/user/Users.vue'
import Roles from '@/components/role/Roles.vue'
import GoodsCate from '@/components/goods/GoodsCate.vue'
import GoodsList from '@/components/goods/GoodsList.vue'
import store from '@/store'
//引入组件
const userRule = { path: '/users', component: Users }
const roleRule = { path: '/roles', component: Roles }
const goodsRule = { path: '/goods', component: GoodsList }
const categoryRule = { path: '/categories', component: GoodsCate }
//声明变量与vuex中的二级权限的path进行映射关系
const ruleMapping = {
'users': userRule,
'roles': roleRule,
'goods': goodsRule,
'categories': categoryRule
}
export function initDynamicRoutes() {
//根据二级权限对路由规则的动态添加
console.log(router) //这个是路由下的所有路由规则,打印出来更清晰的使用里面的方法
const currentRoutes = router.options.routes
const rightList = store.state.rightList
rightList.forEach(item => {
item.children.forEach(item => {
//对二级权限的路由遍历和动态添加
const itemRule = ruleMapping[item.path]
itemRule.meta = item.rights
//对路由新建规则并赋值,返回数据的按钮权限
currentRoutes[2].children.push(itemRule)
})
})
router.addRoutes(currentRoutes)
}
注意BUG
:此处写完路由文件后,页面刷新为空
原因:页面刷新,路由重新加载,路由规则变成空,动态路由不存在了
解决:登陆后添加动态路由,可以放在根组件App.vue中
App.vue代码:
import { initDynamicRoutes } from '@/router.js'
created() {
initDynamicRoutes()
}
//在组件创建时执行这个方法即可解决
login.vue代码:
import { initDynamicRoutes } from '@/router.js'
//登陆成功后调用下这个方法即可
initDynamicRoutes()
3.按钮的控制
原理:根据返回的数据存储在子路由的权限(增删改查)
实现: 使用自定义指令来实现
在src目录下的utils新建permission文件,并且在main.js中调用
permission.js代码:
import Vue from 'vue'
import router from '@/router.js'
Vue.directive('permission', {
//注册自定义指令'v-permission'
inserted: function (el, binding) {
//当指令插入元素调用时,可以传递两个值:el:指令所在的元素;binding:一个对象,包含当前元素的propety属性
console.log(el)
console.log(binding)
const action = binding.value.action
//此按钮指令中的值
const currentRight = router.currentRoute.meta
//此变量为路由动态添加后的权限的值
console.log(currentRight)
if (currentRight) {
if (currentRight.indexOf(action) === -1) {
// 不具备权限,按钮应该为禁用或者不存在
const type = binding.value.effect
if (type === 'disabled') {
el.disabled = true //激活禁用
el.classList.add('is-disabled')
} else {
el.parentNode.removeChild(el) //删除
}
}
}
}
})
页面中使用:
删除
//注:如果存在effect:'disabled表示为禁用状态,否则为当前按钮为删除状态,不显示
4.请求响应的控制
- 请求的控制
(1)除了登陆注册外的请求都要携带token,用于数据库鉴别身份
实现:设置请求头
(2)如果发出了非权限内的请求,应该在前端进行阻断
原理:根据请求方式的映射关系进行判断
请求方式:
查看 get请求
增加 post请求
修改 put请求
删除 delete请求
http.js代码:
import router from '../router'
const actionMapping = {
get: 'view',
post: 'add',
put: 'edit',
delete: 'delete'
}
axios.interceptors.request.use(function (req) {
const currentUrl = req.url
if (currentUrl !== 'login') {
req.headers.Authorization = sessionStorage.getItem('token')
// 当前模块中具备的权限
const method = req.method
// 根据请求, 得到是哪种操作
const action = actionMapping[method]
// 判断action是否存在当前路由的权限中
const rights = router.currentRoute.meta
if (rights && rights.indexOf(action) == -1) {
// 没有权限
alert('没有权限')
return Promise.reject(new Error('没有权限'))
}
}
return req
})
- 响应的控制
如果得到服务器的返回状态码为401,代表token超时或被篡改,此时应跳转到登陆页面
http.js代码:
axios.interceptors.response.use(function (res) {
if (res.data.meta.status === 401) {
router.push('/login')
sessionStorage.clear()
window.location.reload()
}
return res
})