前端權(quán)限控制和管理實(shí)現(xiàn)步驟詳解
1.前言
在Web系統(tǒng)中,一直以來權(quán)限都只是后端程序所控制的。因?yàn)閃eb 系統(tǒng)圍繞的是數(shù)據(jù),而和數(shù)據(jù)庫最緊密接觸的是后端程序。所以在很長的一段時(shí)間內(nèi),權(quán)限一直都只是后端程序要考慮的話題。 但是隨著前后端分離架構(gòu)的流行,越來越多的項(xiàng)目也在前端進(jìn)行權(quán)限控制。
2.權(quán)限相關(guān)概念
2.1權(quán)限的分類
(1)后端權(quán)限
從根本上講前端僅僅只是視圖層的展示,權(quán)限的核心是在于服務(wù)器中的數(shù)據(jù),所以后端才是權(quán)限的關(guān)鍵,后端權(quán)限可以控制某個(gè)用戶是否能夠查詢數(shù)據(jù),是否能夠修改數(shù)據(jù)等操作。
ps:后端如何知道該請求是哪個(gè)用戶發(fā)過來的
- cookie
- session
- token
ps:后端的權(quán)限設(shè)計(jì)RBAC(一般五張表)
- 用戶
- 角色
- 權(quán)限
- 還有兩張關(guān)系表
(2)前端權(quán)限
本質(zhì)上來說,前端權(quán)限的控制就是控制前端的視圖層的展示和前端所發(fā)送的請求。但是只有前端權(quán)限控制沒有后端權(quán)限控制是萬萬不可的。 前端權(quán)限控制只是達(dá)到錦上添花的效果。
2.2前端權(quán)限的意義
如果僅從能夠修改服務(wù)器中數(shù)據(jù)庫中的數(shù)據(jù)層面上講,確實(shí)只在后端做控制就足夠了,那為什么越來越多的項(xiàng)目也進(jìn)行了前端權(quán)限的控制,主要有這幾方面的好處。
- 降低非法操作的可能性;
- 不怕贓偷就怕賊惦記,在頁面中展示出一個(gè)就算點(diǎn)擊了也最終會失敗的按鈕,勢必會增加有心者非法操作的可能性;
- 盡可能排除不必要清求,減輕服務(wù)器壓力,沒必要的請求,操作失敗的清求,不具備權(quán)限的清求,這些應(yīng)該壓根就不需要發(fā)送,請求少了,自然也會減輕服務(wù)器的壓力;
- 提高用戶體驗(yàn);
- 根據(jù)用戶具備的權(quán)限為該用戶展現(xiàn)自己權(quán)限范圍內(nèi)的內(nèi)容,避免在界面上給用戶帶來困擾,讓用戶專注于分內(nèi)之事;
3.前端權(quán)限控制思路
3.1菜單的權(quán)限控制
在登錄請求中,會得到用戶的權(quán)限數(shù)據(jù),當(dāng)然,這個(gè)需要后端返回?cái)?shù)據(jù)的支持。前端根據(jù)權(quán)限數(shù)據(jù),展示對應(yīng)的菜單。點(diǎn)擊菜單,才能查看相關(guān)的界面。
3.2界面的權(quán)限控制
如果用戶沒有登錄,手動在地址欄敲入登錄后主界面的地址,則需要跳轉(zhuǎn)到登錄界面。
如果用戶已經(jīng)登錄,如果手動敲入非權(quán)限內(nèi)的地址,則需要跳轉(zhuǎn)404 界面。
3.3按鈕的權(quán)限控制
在某個(gè)菜單的界面中,還得根據(jù)權(quán)限數(shù)據(jù),展示出可進(jìn)行操作的按鈕,比如刪除、修改、增加等按鈕。
3.4接口的權(quán)限控制
如果用戶通過非常規(guī)操作,比如通過瀏覽器調(diào)試工具將某些禁用的按鈕變成啟用狀態(tài),此時(shí)發(fā)的請求也應(yīng)該被前端所攔截。
4.實(shí)現(xiàn)步驟
4.1菜單欄控制
用戶登錄之后,服務(wù)端返回一個(gè)數(shù)據(jù),這個(gè)數(shù)據(jù)有菜單列表和token
,我們把這個(gè)數(shù)據(jù)放入到vuex
中,然后主頁根據(jù)vuex
中的數(shù)據(jù)進(jìn)行菜單列表的渲染。
問題:刷新界面后vuex
數(shù)據(jù)會消失,菜單欄會消失解決:將數(shù)據(jù)存儲在sessionStorage
中,并讓其和vuex
中的數(shù)據(jù)保持同步(用專門的持久化插件也可以)store/index.js:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { //每次點(diǎn)擊頁面中的刷新按鈕,state中的數(shù)據(jù)就會根據(jù)下面的數(shù)據(jù)重新初始化 rightList: JSON.parse(sessionStorage.getItem('rightList') || '[]'), username: sessionStorage.getItem('username'), }, mutations: { setRightList(state, newData) { state.rightList = newData sessionStorage.setItem('rightList', JSON.stringify(newData)) //sessionStorage只能存儲字符串 }, setUsername(state, newData) { state.username = newData sessionStorage.setItem('username', newData) }, }, actions: {}, getters: {}, })
4.2界面的控制
登錄成功后,將token
數(shù)據(jù)存儲在sessionStorage
中,用來判斷是否登錄
(1)路由導(dǎo)航守衛(wèi)
router/index.js:
router.beforeEach((to, from, next) => { //頁面跳轉(zhuǎn)之前做攔截動作,判斷token是否存在 if (to.path === '/login') next() else { const token = sessionStorage.getItem('token') if (!token) next('/login') else { next() } } })
問題:這樣用戶在登錄之后就可以訪問其他界面了,但如果用戶A登錄之后只能訪問a頁面不能訪問b頁面,但是這時(shí)候他還是可以通過地址欄輸入進(jìn)入到b頁面。
解決:當(dāng)然我們也可以設(shè)置路由導(dǎo)航守衛(wèi),但是如果有多個(gè)頁面,設(shè)置會非常不方便,并且對于用戶A來說,它是不用訪問b頁面的,這時(shí)候我們可以對A不顯示b頁面,這個(gè)時(shí)候我們就用到了動態(tài)路由。
(2)動態(tài)路由
根據(jù)當(dāng)前用戶所擁有的的權(quán)限數(shù)據(jù)來動態(tài)添加所需要的路由。
先定義好所有的路由規(guī)則:router/routerMap.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' 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 routesMap = { users: userRule, roles: roleRule, goods: goodRule, categories: categoryRule, } export { routesMap }
登錄成功之后動態(tài)添加路由,注意這個(gè)initDynamicRoutes
的方法需要暴露出去在登錄頁面調(diào)用
這樣當(dāng)用戶A在地址欄輸入自己不能訪問的路由時(shí),就不會跳轉(zhuǎn)到該頁面,而是跳轉(zhuǎn)到404頁面。
問題:如果我們重新刷新的話動態(tài)路由就會消失,動態(tài)路由是在登錄成功之后才會調(diào)用,刷新的時(shí)候并沒有調(diào)用,所以動態(tài)路由沒有添加上。
解決:可以在app.vue
中的created中
調(diào)用添加動態(tài)路由的方法
4.3按鈕的控制
雖然用戶可以看到某些界面了,但是對于這個(gè)界面的一些按鈕,該用戶可能是沒有權(quán)限的。 因此,我們需要對組件中的一些按鈕進(jìn)行控制,用戶不具備權(quán)限的按鈕就隱藏或者禁用,而在這塊的實(shí)現(xiàn)中,可以把該邏輯放到自定義指令中
比如我們可以根據(jù)后端返回的數(shù)據(jù)right來判斷用戶有什么權(quán)限,如下圖。
添加自定義指令 控制按鈕:
import Vue from 'vue' import store from '../store'; import router from '../router'; const permission = { inserted(el, binding) { //傳過來的參數(shù)中,action表示被綁定的按鈕是什么操作;effect表示當(dāng)用戶沒有這個(gè)操作權(quán)限的時(shí)候, //應(yīng)該如何如理這個(gè)按鈕 const action = binding.value.action const effect = binding.value.effect //獲取用戶在當(dāng)前路由中所具有的權(quán)限列表 let currentPathPermissions = router.currentRoute.meta if(currentPathPermissions.indexOf(action) == -1){ if(effect === 'disabled'){ el.disabled = true el.classList.add('is-disabled') //element-ui需要的處理 } else el.parentNode.removeChild(el) //el.style.display = 'none' } } } Vue.directive('permission', permission)
4.4接口的控制
(1)請求控制
除了登錄請求都得要帶上token,這樣服務(wù)器才可以鑒別你的身份。這塊需要配置axios的請求攔截器。
如果發(fā)出了非權(quán)限內(nèi)的請求,應(yīng)該直接在前端范圍內(nèi)阻止,雖然這個(gè)請求發(fā)到服務(wù)器也會被拒絕。
非權(quán)限內(nèi)的請求:比如a用戶是不能夠操作該頁面的按鈕的,但是他通過f12調(diào)試把按鈕改為可點(diǎn)擊,如果我們不對這個(gè)請求進(jìn)行處理,那么這個(gè)請求就會發(fā)送出去。
//當(dāng)前模塊中具備的權(quán)限關(guān)系映射 const actionMapping = { get: 'view', //查看-->get請求 post: 'add', //添加-->post請求 put: 'edit', //編輯-->put請求 delete: 'delete', //刪除-->delete請求 } //請求攔截控制 axios.interceptors.request.use((request) => { // console.log(request.url); // console.log(request.method); if (request.url !== 'login') { //除了登錄請求以外,在請求頭中全部添加上token request.headers.Authorization = sessionStorage.getItem('token') //將請求方式映射成為操作類型的權(quán)限 const action = actionMapping[request.method] //獲取用戶在當(dāng)前路由下所具有的權(quán)限 const currentRight = router.currentRoute.meta if (currentRight && currentRight.indexOf(action) === -1) { //沒有權(quán)限發(fā)送請求,通過報(bào)錯攔截 alert('沒有權(quán)限!') return Promise.reject(new Error('沒有權(quán)限')) } } return request })
(2)響應(yīng)控制
得到了服務(wù)器返回的狀態(tài)碼401,代表token 超時(shí)或者被篡改了,此時(shí)應(yīng)該強(qiáng)制跳轉(zhuǎn)到登錄界面。
axios.interceptors.response.use((response) => { if (response.data.meta.status === 401) { //返回401表示token失效,返回登陸界面 router.push('/login') sessionStorage.clear() window.location.reload() } return response })
5.小結(jié)
- 前端權(quán)限的實(shí)現(xiàn)必須要后端提供數(shù)據(jù)支持,否則無法實(shí)現(xiàn)。
- 返回的權(quán)限數(shù)據(jù)的結(jié)構(gòu),前后端需要溝通協(xié)商怎樣的數(shù)據(jù)用起來才最方便。
5.1菜單控制
- 權(quán)限的數(shù)據(jù)需要在多組件之間共享,因此采用
vuex
。 - 防止刷新界面權(quán)限數(shù)據(jù)丟失,所以需要使用
sessionStorage
進(jìn)行持久化,并目要保證兩者的同步。
5.2界面控制
- 路由的導(dǎo)航守衛(wèi)可以防止跳過登錄界面。
- 動態(tài)路由可以讓不具備權(quán)限的界面的路由規(guī)則壓根就不存在。
5.3按鈕控制
- 路由規(guī)則中可以增加路由元數(shù)據(jù)meta。
- 通過路由對象可以得到當(dāng)前的路由規(guī)則以及存在此規(guī)則中的meta數(shù)據(jù)。
- 自定義指令可以很方便的實(shí)現(xiàn)按鈕控制。
5.4請求和響應(yīng)控制
- 請求攔截器和響應(yīng)攔截器的使用。
- 請求方式的約定
restful
。
總結(jié)
到此這篇關(guān)于前端權(quán)限控制和管理實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)前端權(quán)限控制和管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javaScript遍歷對象和數(shù)組的方法總結(jié)
這篇文章介紹了javaScript遍歷對象和數(shù)組的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06詳解webpack 入門總結(jié)和實(shí)踐(按需異步加載,css單獨(dú)打包,生成多個(gè)入口文件)
本篇文章主要介紹了webpack 入門總結(jié)和實(shí)踐(按需異步加載,css單獨(dú)打包,生成多個(gè)入口文件) ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06gulp-htmlmin壓縮html的gulp插件實(shí)例代碼
這篇文章主要介紹了gulp-htmlmin壓縮html的gulp插件實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-06-06js+html獲取系統(tǒng)當(dāng)前時(shí)間
這篇文章主要為大家詳細(xì)介紹了javascript html獲取系統(tǒng)當(dāng)前時(shí)間,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11JavaScript中如何跳出forEach循環(huán)代碼示例
循環(huán)遍歷一個(gè)元素是開發(fā)中最常見的需求之一,下面這篇文章主要給大家介紹了關(guān)于JavaScript中如何跳出forEach循環(huán)的相關(guān)資料,文章通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-06-06