權(quán)限相關(guān)的概念
某個用戶是否有對某個數(shù)據(jù)的增刪改查等操作的權(quán)限
后端權(quán)限(對數(shù)據(jù)庫中的數(shù)據(jù)的控制)
1.權(quán)限的核心在于服務(wù)器中數(shù)據(jù)的變化,后端權(quán)限可以控制某個用戶是否可以對數(shù)據(jù)進(jìn)行增刪改查的操作
后端如何知道請求是哪個用戶發(fā)送的凳兵?亏钩?
狀態(tài)保持:
cookie,session,token
一般而言前后端分離開發(fā)采用token
來鑒權(quán)
2.后端權(quán)限設(shè)計RBAC(基于角色的權(quán)限控制)
用戶==>角色==>權(quán)限
前端權(quán)限(視覺層面的控制)
主要用來控制前端視圖層的展示與前端所發(fā)送的請求
意義:
- 降低非法操作的可能性
- 排除不必要的請求,減輕服務(wù)器的壓力
- 提高用戶的體驗
前端權(quán)限控制的思路:(不限制某一技術(shù)涧团,是個解決方案)
1.菜單的控制(后臺管理系統(tǒng)中側(cè)邊欄的展示)
在登陸請求中狸剃,會得到權(quán)限的數(shù)據(jù)贫导,需要后端來返回數(shù)據(jù)作為支持,前端根據(jù)后端返回的數(shù)據(jù)展示對應(yīng)的菜單附较,點擊菜單,查看對應(yīng)的界面
2.界面的控制
如果用戶沒有登陸潦俺,手動在地址欄輸入管理界面的地址拒课,需要跳轉(zhuǎn)到登陸界面
如果用戶已經(jīng)登陸,手動輸入會權(quán)限內(nèi)的地址事示,則需要跳轉(zhuǎn)到404界面
3.按鈕的控制
在某個菜單的界面中早像,需要根據(jù)權(quán)限數(shù)據(jù)展示可操作的按鈕
4.請求與響應(yīng)的控制
如果用戶通過非常規(guī)的操作,如通過瀏覽器的調(diào)試工具將禁用的按鈕變?yōu)榭捎眯ぞ簦l(fā)送請求卢鹦,也應(yīng)該被攔截,盡管后端會返回錯誤的狀態(tài)碼劝堪,目的是減輕服務(wù)器的請求次數(shù)
Vue權(quán)限控制的實現(xiàn):
1.菜單的控制
前提:登陸成功后拿到的對應(yīng)的數(shù)據(jù)
{
"data":{
"email": "123999@qq.com",
"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": "系統(tǒng)管理",
"children": [
{
"authName": "角色管理",
"id": 172,
"path": "role",
"rights":["view","edit","add","delete"]
},
],
"icon": "icon-shangpin",
"id": 170,
},
]
}
- token用于前端頁面的用戶狀態(tài)的保持
- right數(shù)組冀自,是該用戶具備的權(quán)限數(shù)據(jù),一級權(quán)限對應(yīng)一級菜單秒啦,二級權(quán)限對應(yīng)二級菜單
注意:這些數(shù)據(jù)是在登陸界面獲取到凡纳,如果想在首頁使用,需要用到vuex來做全局的狀態(tài)管理
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中,頁面中也可能用到當(dāng)前用戶的信息葛超,需要的話進(jìn)行存儲暴氏,這里只用到了用戶名
//此處需要注如果沒有對state中的值進(jìn)行本地的存儲會出現(xiàn)一個bug:頁面刷新,vuex中的數(shù)據(jù)初始化绣张,會變?yōu)榭沾鹩妫恍枰獙?shù)據(jù)存儲在本地存儲中,使其與vuex中的數(shù)據(jù)保持同步即可解決
},
//state存儲值侥涵,不建議在這里面進(jìn)行更改沼撕,要更改使用mutations來定義方法進(jìn)行更改
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代碼:
//登陸成功后的回調(diào)中進(jìn)行返回信息的存儲
this.$store.commit("setRightList", res.right);
this.$store.commit("setUsername", res.data.username);
sessionStorage.setItem("token", res.data.token);
//token就不需要存儲在vuex中,只需要存儲在本地即可芜飘,在頁面需要退出登陸時清空本地存儲的值务豺,進(jìn)行路由的跳轉(zhuǎn)并刷新頁面即可將本地存儲與vuex中存儲的值清空
index代碼:
import { mapState } from "vuex";
//引入mapState函數(shù)并對vuex中的數(shù)據(jù)做一個映射
created() {
this.menulist = this.rightList;
this.msg=this.username
//映射出來的值直接賦值使用即可
},
computed: {
...mapState(['rightList', 'username']),
},
//退出登陸清空vuex與本地存儲的數(shù)據(jù),在退出登陸的方法中寫入一下即可:
logout() {
sessionStorage.clear();
setTimeout(() => {
this.$router.push("/login");
this.$message.success("退出登陸成功");
}, 1000);
//設(shè)置延遲執(zhí)行函數(shù)為了用戶體驗
},
2.界面的控制
- 未登錄直接訪問
如何判斷用戶是否登陸嗦明?(token)
前面已經(jīng)進(jìn)行token的存儲
什么時機進(jìn)行判斷笼沥?(路由導(dǎo)航守衛(wèi))
router.js代碼:
router.beforeEach((to, from, next) => {
if (to.path === '/login'||to.path==='/register') {
//如果是跳轉(zhuǎn)到登陸或者注冊是不用進(jìn)行判斷
next()
} else {
const token = sessionStorage.getItem('token')
if(!token) {
//跳轉(zhuǎn)除登陸注冊組件時進(jìn)行token的判斷,如果不存在跳轉(zhuǎn)登陸頁面進(jìn)行登陸
next('/login')
} else {
next()
}
}
})
- 用戶已經(jīng)登陸但是在地址欄輸入不具備權(quán)限的頁面仍然可以訪問!
解決:
(1).使用路由導(dǎo)航守衛(wèi)
固然是可以的奔浅,可以在每次地址欄變化時從vuex中取出right進(jìn)行判斷用戶將要訪問的界面這個用戶是否有權(quán)限進(jìn)行訪問馆纳。
如果用戶不具備權(quán)限的路由是否應(yīng)該是不存在的?汹桦?
(2).動態(tài)路由的使用
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中的二級權(quán)限的path進(jìn)行映射關(guān)系
const ruleMapping = {
'users': userRule,
'roles': roleRule,
'goods': goodsRule,
'categories': categoryRule
}
export function initDynamicRoutes() {
//根據(jù)二級權(quán)限對路由規(guī)則的動態(tài)添加
console.log(router) //這個是路由下的所有路由規(guī)則鲁驶,打印出來更清晰的使用里面的方法
const currentRoutes = router.options.routes
const rightList = store.state.rightList
rightList.forEach(item => {
item.children.forEach(item => {
//對二級權(quán)限的路由遍歷和動態(tài)添加
const itemRule = ruleMapping[item.path]
itemRule.meta = item.rights
//對路由新建規(guī)則并賦值,返回數(shù)據(jù)的按鈕權(quán)限
currentRoutes[2].children.push(itemRule)
})
})
router.addRoutes(currentRoutes)
}
注意BUG
:此處寫完路由文件后舞骆,頁面刷新為空
原因:頁面刷新钥弯,路由重新加載,路由規(guī)則變成空葛作,動態(tài)路由不存在了
解決:登陸后添加動態(tài)路由寿羞,可以放在根組件App.vue中
App.vue代碼:
import { initDynamicRoutes } from '@/router.js'
created() {
initDynamicRoutes()
}
//在組件創(chuàng)建時執(zhí)行這個方法即可解決
login.vue代碼:
import { initDynamicRoutes } from '@/router.js'
//登陸成功后調(diào)用下這個方法即可
initDynamicRoutes()
3.按鈕的控制
原理:根據(jù)返回的數(shù)據(jù)存儲在子路由的權(quán)限(增刪改查)
實現(xiàn): 使用自定義指令來實現(xiàn)
在src目錄下的utils新建permission文件,并且在main.js中調(diào)用
permission.js代碼:
import Vue from 'vue'
import router from '@/router.js'
Vue.directive('permission', {
//注冊自定義指令'v-permission'
inserted: function (el, binding) {
//當(dāng)指令插入元素調(diào)用時赂蠢,可以傳遞兩個值:el:指令所在的元素绪穆;binding:一個對象,包含當(dāng)前元素的propety屬性
console.log(el)
console.log(binding)
const action = binding.value.action
//此按鈕指令中的值
const currentRight = router.currentRoute.meta
//此變量為路由動態(tài)添加后的權(quán)限的值
console.log(currentRight)
if (currentRight) {
if (currentRight.indexOf(action) === -1) {
// 不具備權(quán)限虱岂,按鈕應(yīng)該為禁用或者不存在
const type = binding.value.effect
if (type === 'disabled') {
el.disabled = true //激活禁用
el.classList.add('is-disabled')
} else {
el.parentNode.removeChild(el) //刪除
}
}
}
}
})
頁面中使用:
<el-button v-permission={action:'add',effect:'disabled'}>刪除</el-button>
//注:如果存在effect:'disabled表示為禁用狀態(tài)玖院,否則為當(dāng)前按鈕為刪除狀態(tài),不顯示
4.請求響應(yīng)的控制
- 請求的控制
(1)除了登陸注冊外的請求都要攜帶token第岖,用于數(shù)據(jù)庫鑒別身份
實現(xiàn):設(shè)置請求頭
(2)如果發(fā)出了非權(quán)限內(nèi)的請求难菌,應(yīng)該在前端進(jìn)行阻斷
原理:根據(jù)請求方式的映射關(guān)系進(jìn)行判斷
請求方式:
查看 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')
// 當(dāng)前模塊中具備的權(quán)限
const method = req.method
// 根據(jù)請求, 得到是哪種操作
const action = actionMapping[method]
// 判斷action是否存在當(dāng)前路由的權(quán)限中
const rights = router.currentRoute.meta
if (rights && rights.indexOf(action) == -1) {
// 沒有權(quán)限
alert('沒有權(quán)限')
return Promise.reject(new Error('沒有權(quán)限'))
}
}
return req
})
- 響應(yīng)的控制
如果得到服務(wù)器的返回狀態(tài)碼為401,代表token超時或被篡改蔑滓,此時應(yīng)跳轉(zhuǎn)到登陸頁面
http.js代碼:
axios.interceptors.response.use(function (res) {
if (res.data.meta.status === 401) {
router.push('/login')
sessionStorage.clear()
window.location.reload()
}
return res
})