0.前言
記得當年面試的時候,面試官問我,前端怎么做權限控制,咱也不太會這個,只能尷尬回答道:“都是老大搭的架子,我只負責寫業務模塊代碼”。
如今自己也做了很多項目了,覺得有必有對前端權限控制做一個總結。
前端權限控制一直是前端必須掌握的一個知識點,一般來說稍微正規一點的后台系統肯定有權限控制。當然還是那句老話,前端本來就是不安全的,真正的安全還是需要后端兄弟去把關,所以后端也必須按做權限控制!我們前端的權限校驗主要的目的是過濾不該有的請求和操作,減少服務端壓力。
我個人認為前端權限控制應該分為四個方面,接口權限、按鈕權限,頁面權限,路由權限,下面就分四個部分探討下權限控制怎么做
1.接口權限
原則
接口權限最簡單,目前一般采用jwt的形式來驗證,沒有通過的話一般返回401 Authentication Required
登錄完拿到Token,將token存起來(cookie或者ssessionStorage),每次登錄的時候頭部攜帶token就行了(axios請求攔截器實現)。
偽碼實現
const {token} = login()
cookie.set('token',token) axios.interceptors.request.use(config => { config.headers['token'] = cookie.get('token') return config })
2.按鈕權限
原則
一個頁面會有新增,刪除,編輯等等按鈕。不同用戶應該是有不同操作權限的。
我們不妨定義權限碼 0:不可見 1:不可編輯 2:可編輯
我們提前和后端約定好按鈕的名字,后端會返回一個按鈕權限列表。然后我們根據權限列表使用v-if指令或者 綁定disabled屬性達到相應權限效果。
當然更好的最好是自己寫一個自定義權限指令,實質就是根據相應權限操作dom
偽碼實現
比如概覽頁面的編輯按鈕 名字先和后端定義好叫做overview-edit
// overviwe.vue overview是概覽頁面的路由名 ... <button v-auth='edit'> ... //util.js 全局注冊自定義指令 vue.directive('auth', { inserted: function (el, binding, vnode) { const optName = binding.arg const authName = `${routeName}-${optName}` //這里根據路由名和操作類型拼出按鈕名 overview-edit const btnAuthList = store.state.auth.btnAuthList if (btnAuthList[authName]===0) { // 按鈕權限為0則移除dom el.parentNode.removeChild(el) } else if (btnAuthList[authName]===1) { // 按鈕權限為1則禁用按鈕 vnode.componentInstance.disabled = true } } }) // 登錄的時候接受按鈕權限並存在vuex里面 const {btnAuthList} = login() vuex.state.btnAuthList = btnAuthList
3.頁面權限(菜單權限)
個人認為頁面權限實際上就是菜單權限,如果說我們沒有去某個頁面的導航菜單,實際上就是沒有去那個頁面的權限了,所以說頁面權限的實際就是菜單權限。
原則
一句話,獲取菜單權限列表,動態遞歸生成菜單
這個菜單權限列表可以是后台直接返回你的,也可以是你注冊路由的時候寫在meta里面的菜單信息,后台返回路由權限,你根據meta信息動態算出的菜單權限。
至於菜單肯定是根據菜單權限遞歸生成的
偽碼實現
//如果是定義在route信息里面會是這種樣子 //我們可以根據后端返回的路由權限結合meta算出菜單權限 { name:xxx path:xxx meta: { role: [xxx,xxx,xxx] //哪些角色有資格 MenuIcon: 'xxxx' //菜單圖標 MenuTitle: 'xxx' //菜單名 } } //當然也可以麻煩后台直接生成菜單權限返回來 const {menuList} = login() //存vuex里 vuex.state.menuList = menuList //在側邊欄或者頂部菜單組件里動態生成菜單 //這里基本都是用的UI庫,比如element-ui的NavMenu來實現的,大家有興趣可以自己看文檔,當然也可以自己遞歸實現,不難 <navMenu/ :menuList=menuList>
4.路由權限
上面的菜單權限雖然做到能看不見菜單,但是我可以通過直接輸入url的方式去沒有權限的頁面呀,這種情況需要靠我們的路由權限來阻止。
原則
這里有兩個方案
第一種,也是我目前項目用的,先注冊好所有的路由,然后獲取有資格訪問的路由權限列表,最后直接通過Router.beforeEach來判斷,每次跳路由的時候判斷是否在權限列表里,在的話就放行,不在就提示權限不夠
優點:簡單暴力,不會跳到404頁面(因為去的路由能在路由規則里找到)
缺點:由於初始化了所有路由,運行的時候會掛載不必要的路由(?有待考究)
第二種,先只注冊基本路由,然后獲取路由權限列表,然后借助route.add() API根據權限列表將有權限的路由動態注冊到路由規則上
優缺點與第一種正好相反
偽碼實現
這里只寫第一種方案,第二種大家自行google
const {routeAuthList} = login() cosnt whiteList = ['/login','/','/404'] //合並生成總路由 whiteList = whiteList.contact(routeAuthList) //存vuex vuex.state.whiteList = whiteList //路由守衛判斷 router.beforeEach((to, from, next) => { //權限校驗 let pass = whiteList.inclue(to); if(!pass){ return console.log('無權訪問'); } next(); });
資源搜索網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com
5.討論
后端返回什么
這個我覺得實際沒有定論,相關的文章也讀了許多,什么做法都有,還是需要結合實際業務。
比如
1.按鈕權限特別少的,那么后端不用返回按鈕權限,直接前端生成鈕權限就行。甚至不用使用自定義指定,直接v-if,disable就行
2.權限頁面特別多,次級路由也特別多,那么也可以前端這邊生成路由權限,因為如果后端返的話,每次定義一個次級頁面都得讓后端在數據庫加一條數據,太麻煩人了,不方便
3.后端也不是說非得返回權限路由列表的,像我目前的項目就是返回的菜單列表,然后我根據菜單列表名手動算出權限列表。其實都一樣,返回權限列表也是根據權限列表里面的meta算出菜單列表,有毛區別?
緩存什么
獲取的個人信息(包括權限列表)該不該緩存到ssessionStorage里面?我看很多人的文章都是只緩存token,每次刷新都是重新拉取信息。
個人認為這樣做意義不大,緩存的目的就是為了減少請求,優化交互。存在當前頁簽基本能保證同一時間你就是你。再說防君子不防小人,至於真的是小人,篡改ssessionStorage數據,咱不是還有后台兄弟的校驗嗎,怕個卵。