0.背景和目的
后臺(tái)管理系統(tǒng)一般都少不了登錄權(quán)限控制璧微,這里我們討論以下如何實(shí)現(xiàn)權(quán)限控制嗦玖。
1.主要概念
一個(gè)完整的權(quán)限控制模塊涉及較多,一般包括用戶管理妈经、角色管理淮野、資源管理(即菜單管理,當(dāng)前系統(tǒng)總共有哪些菜單)吹泡。
用戶
這個(gè)沒什么好說的骤星,就是你登錄時(shí)候的賬號(hào),用戶有自己的信息爆哑,比如姓名洞难,賬號(hào)密碼,手機(jī)號(hào)等揭朝,用戶登錄之后會(huì)拿到用于區(qū)別用戶身份的token队贱,一般token會(huì)放在請(qǐng)求的接口的header里面,供后端來確定當(dāng)前用戶潭袱。
一個(gè)用戶可以屬于一個(gè)或者多個(gè)角色露筒,用戶會(huì)獲得角色下的權(quán)限。
角色
角色可以理解為具有某些權(quán)限的一類用戶敌卓,比如管理員角色慎式,用于維護(hù)整個(gè)系統(tǒng),具有最高權(quán)限,普通操作人員可能只具有某些業(yè)務(wù)的普通操作權(quán)限瘪吏。
權(quán)限菜單
權(quán)限菜單可以理解為資源癣防,比如當(dāng)前有哪些菜單,哪些功能掌眠,功能下面有哪些按鈕等蕾盯。
權(quán)限有不同的粒度,比如有的系統(tǒng)相對(duì)簡(jiǎn)單只需要控制到具體頁(yè)面即可蓝丙,不再具體控制該頁(yè)面下的具體按鈕级遭。
還有更進(jìn)一步,除了菜單按鈕之外渺尘,還可以控制到具體業(yè)務(wù)數(shù)據(jù)挫鸽。
本篇只討論控制到菜單和按鈕級(jí)別。
2.核心流程
從流程上來說用戶權(quán)限包括兩部分:設(shè)定權(quán)限鸥跟、拉取權(quán)限
設(shè)定權(quán)限
設(shè)定系統(tǒng)菜單
即當(dāng)前系統(tǒng)的功能菜單丢郊、按鈕管理,可以做成一個(gè)功能医咨,如果系統(tǒng)簡(jiǎn)單也可以手動(dòng)編輯數(shù)據(jù)庫(kù)實(shí)現(xiàn)枫匾。
設(shè)定角色下的權(quán)限
由用戶來勾選角色下包括的菜單、按鈕拟淮。
設(shè)定用戶所屬角色
用戶可以屬于一個(gè)或幾個(gè)角色干茉,用戶的權(quán)限是所屬的角色最大集合
拉取權(quán)限
這里我們可以在全局狀態(tài)管理(Vuex)設(shè)置一個(gè)狀態(tài)標(biāo)識(shí)(hasLoadAccess)和一個(gè)獲取權(quán)限動(dòng)作(loadAccess)
早期的時(shí)候動(dòng)態(tài)生成路由并不現(xiàn)實(shí),但當(dāng)vue-router增加了addRouters方法之后很泊,這一切變得簡(jiǎn)單了角虫。
當(dāng)用戶拉取權(quán)限之后,根據(jù)得到的數(shù)據(jù)生成路由并addRouters和生成導(dǎo)航菜單撑蚌。
3.服務(wù)端設(shè)計(jì)
數(shù)據(jù)庫(kù)表設(shè)計(jì)
menu表
用于存儲(chǔ)系統(tǒng)中需要權(quán)限驗(yàn)證的路由
id? ? int? 主鍵自增
name String 菜單功能名稱
index String? 標(biāo)識(shí)上遥,用戶匹配本地組件
parentId int 菜單所屬,如果是一級(jí)菜單值為0
isMenu int? ?是否是菜單
這里需要特別注意争涌,因?yàn)橛行┞酚蓪儆诓藛畏鄢行┞酚刹粚儆诓藛危热缬脩艄芾眄?yè)面是需要顯示在導(dǎo)航菜單中亮垫,但用戶新增模软、用戶編輯不應(yīng)該出現(xiàn)在導(dǎo)航菜單中,這里我們都當(dāng)作路由處理饮潦,有一個(gè)好處就是用戶刷新可以停留在當(dāng)前頁(yè)面燃异,邏輯相對(duì)簡(jiǎn)單,當(dāng)然也可以不使用路由來處理這種編輯的頁(yè)面继蜡,比如通過對(duì)話框回俐、抽屜等逛腿,根據(jù)實(shí)際交互設(shè)計(jì)選擇
icon String 結(jié)合iconfront 相應(yīng)圖標(biāo)的類名
container String 容器組件標(biāo)識(shí),這里我們討論二層路由仅颇,container為第一層組件
path String 路由单默,絕對(duì)路徑
sort int 排序,用于控制菜單的順序忘瓦,可以手動(dòng)修改
按鈕表
存儲(chǔ)系統(tǒng)中的所有按鈕
id int 主鍵
uuid string 唯一標(biāo)識(shí)
name String 按鈕名稱
parentId int 所屬菜單的id
角色表
id int 主鍵id
name String 角色名稱
sort int? 順序
角色權(quán)限表
id int 主鍵id
roleId int 角色I(xiàn)d
menuId int 菜單Id
用戶表
id int 主鍵id
name String 用戶姓名
username String 用戶名
password String 加密碼存儲(chǔ)的密碼
phone string 電話號(hào)碼 不是必須有的字段搁廓,根據(jù)業(yè)務(wù)需求設(shè)定
用戶角色關(guān)系表
id int? 主鍵
userId int? 用戶id
roleId int 角色id
權(quán)限控制少不了后端的配合,除了對(duì)角色耕皮,用戶等的增刪改查外境蜕,還需要提供一個(gè)接口getAccess返回用戶擁有的權(quán)限,返回結(jié)構(gòu)大致如下:
{
code: 0,
????data: [
{
id: 1
index: 'user-management',
name: '用戶管理',
icon: 'icon icon-user',
refer: '/user-management',
? ? ? ? ? ? children: [
{
index:? '',
name: '',
refer: '',
isMenu: true
????????????????}
...
????????????],
...
????????}
????]
}
4.本地路由組織
存儲(chǔ)本地所有路由組件
export default {
? ? 'console': import('path/console.vue'), // 根容器
? ?'index':import('path/index.vue'), //? 以鍵值對(duì)的形式凌停,鍵為數(shù)據(jù)庫(kù)中的index? 根據(jù)index進(jìn)行匹配粱年,值為組件,這里使用import的方式
? ?'user-management': import('path/index.vue')
}
5.權(quán)限獲取
結(jié)合vue-router苦锨,我們可以在路由守衛(wèi)的鉤子函數(shù)beforeEach中執(zhí)行動(dòng)作逼泣,偽代碼如下:
import router from 'vue-router'
import Store from 'vuex'
const whiteList = [? 'login' ]? //不需要權(quán)限的路由
const token = Cookies.get('token')
...
router.beforeEach((to, from, next) => {
? ? if(whiteList.indexOf(to.name) > -1)? {? // 跳轉(zhuǎn)的是不需要登錄狀態(tài)的路由? 直接放行
? ? ? ? next()
????} else if(!token) { // 需要權(quán)限? 但是token不存在? 直接跳轉(zhuǎn)到登錄頁(yè)面
? ? ? ? router.push({path: '/login'})
????} else if(!Store.hasLoadAccess) {? //需要權(quán)限 且token 但沒有還沒有獲取權(quán)限? 則開始獲取權(quán)限
? ? ? ? Strore.loadAccess()
????} else {? // 其余直接跳轉(zhuǎn)到相應(yīng)路由? 這里一定要調(diào)用next 函數(shù)趴泌,不然不能跳轉(zhuǎn)
? ? ? ? next()
????}
})
// 拉取權(quán)限動(dòng)作舟舒,此處在vuex模塊access中
export default {
? ? state: {
? ? ? ? hasLoadAccess: false,
? ? ? ? accessTree: []
????},
mutiation:{
? ? ? ? setLoadStatus(state, status){
? ? ? ? ? ? state.hasLoadAccess = status
? ? ? ? }
}
action: {
? ? ? ? async loadAccess(){
? ? ? ? ? ? const res = await getAccess()
? ? ? ? ? ? if(res.success){
? ? ? ? ? ? ? ? ? ? // 處理路由
????????????}
????????}
????}
}
6.動(dòng)態(tài)生成路由和導(dǎo)航菜單
生成路由
根據(jù)后臺(tái)服務(wù)返回,生成路由數(shù)組嗜憔,路由對(duì)應(yīng)的組件從resourceMap中根據(jù)index進(jìn)行匹配秃励。
并不是所有路由都需要根據(jù)根據(jù)后端數(shù)據(jù)來生成,這里主要是指不需要用戶權(quán)限驗(yàn)證的頁(yè)面吉捶,如登錄頁(yè)面夺鲜、找回密碼等頁(yè)面,這些路由可以寫在前端初始路由里面呐舔。
生成菜單
生成菜單可以放在導(dǎo)航菜單的計(jì)算屬性中币励,或者直接vuex 中的getter中
7.用戶管理、角色管理珊拼、資源管理
此處為正常的增刪改茶沒有需要特別注意的內(nèi)容
8.優(yōu)化
按照以上邏輯即可實(shí)現(xiàn)權(quán)限控制食呻,但是還有以下幾個(gè)問題需要處理
1.當(dāng)跳轉(zhuǎn)到不是菜單對(duì)應(yīng)的路由時(shí),菜單缺少高亮狀態(tài)
可以在menu表中增加refer字段澎现,并存儲(chǔ)到meta中仅胞,設(shè)定當(dāng)前路由應(yīng)該高亮的路由,比如用戶管理頁(yè)面的refer為‘/user-management’,即本身的path剑辫,用戶新增干旧、編輯頁(yè)面的refer也為‘/user-management’
el-menu 組件的default-active設(shè)置為router.meta.refer
2.默認(rèn)路由問題,一般登錄后的默認(rèn)主頁(yè)是home頁(yè)面妹蔽,但如果在菜單設(shè)置的時(shí)候用戶不勾選home頁(yè)面如何處理椎眯。
有兩種處理方式:
1挠将、設(shè)置角色權(quán)限時(shí)home默認(rèn)為選中狀態(tài)且不允許取消選中。
2编整、允許不選擇home菜單捐名,當(dāng)用戶沒有home頁(yè)權(quán)限時(shí),自動(dòng)生成一個(gè)home頁(yè)面闹击,即不包含任何數(shù)據(jù)的歡迎頁(yè)面镶蹋。
3.后端接口過濾問題
這些過濾只是在前端對(duì)用戶的動(dòng)作進(jìn)行限制,但是防君子不防小人赏半,因?yàn)橛脩敉耆梢岳@過前端的這些限制贺归,直接利用postman請(qǐng)求接口,所以權(quán)限限制最終還要后端處理断箫,進(jìn)行用戶驗(yàn)證拂酣。不過這是后端同學(xué)的工作,我們不做討論仲义。
4.資源管理問題
資源管理也就是我們的菜單婶熬、按鈕的管理,如果產(chǎn)品在不斷的迭代埃撵,需要頻繁修改菜單赵颅,或者想把產(chǎn)品做的更晚上,需要一個(gè)資源管理功能暂刘,通過接口來處理菜單邏輯饺谬。
如果資源管理頻率很低,也可以手動(dòng)修改數(shù)據(jù)庫(kù)谣拣,但需要注意的是手動(dòng)修改menu表募寨,不會(huì)處理角色的邏輯關(guān)系,當(dāng)新增或者刪除菜單時(shí)森缠,需要重新保存角色權(quán)限拔鹰,才會(huì)更新角色權(quán)限。
9.結(jié)語(yǔ)
以上是對(duì)vue項(xiàng)目中權(quán)限控制的一種簡(jiǎn)單實(shí)現(xiàn)贵涵,適合小型系統(tǒng)列肢,如果對(duì)于多系統(tǒng)統(tǒng)一認(rèn)證的系統(tǒng),本篇文章的后端部分并不適用独悴,前端部分可以借鑒參考例书。
原創(chuàng)不易,如果本篇文章對(duì)您有所幫助刻炒,還請(qǐng)點(diǎn)贊支持决采。如果您有疑問或者建議,請(qǐng)留言坟奥,我會(huì)在方便的時(shí)候做解答树瞭。