Vue權(quán)限控制

Vue權(quán)限控制

1.權(quán)限相關(guān)概念

1.1.權(quán)限的分類

后端權(quán)限

前端權(quán)限

1.2.前端權(quán)限的意義

降低?法操作的可能性

盡可能排除不必要請(qǐng)求,減輕服務(wù)器壓?

提??戶體驗(yàn)

2.前端權(quán)限控制思路

2.1.菜單的控制

2.2.界?的控制

2.3.按鈕的控制

2.4.請(qǐng)求和響應(yīng)的控制

3. Vue的權(quán)限控制實(shí)現(xiàn)

3.1.菜單的控制

3.2.界?的控制

3.3.按鈕的控制

3.4.請(qǐng)求和響應(yīng)的控制

4.?結(jié)

4.1.菜單控制

4.2.界?控制

4.3.按鈕控制

4.4.請(qǐng)求和響應(yīng)控制

在Web系統(tǒng)中, 權(quán)限很久以來(lái)?直都只是后端程序所控制的,為什么呢? 因?yàn)閃eb系統(tǒng)的本質(zhì)圍繞的是數(shù)據(jù), ?和數(shù)據(jù)庫(kù)最緊密接觸的是后端程序。所以在很?的?段時(shí)間內(nèi),權(quán)限?直都只是后端程序需要考慮的話題。但是隨著前后端分離架構(gòu)的流?,越來(lái)越多的項(xiàng)?也在前端進(jìn)?權(quán)限控制。

1.權(quán)限相關(guān)概念

1.1.權(quán)限的分類

后端權(quán)限

從根本上講前端僅僅只是視圖層的展示, 權(quán)限的核?是在于服務(wù)器中的數(shù)據(jù)變化, 所以后端才是權(quán)限的關(guān)鍵, 后端權(quán)限可以控制某個(gè)?戶是否能夠查詢數(shù)據(jù), 是否能夠修改數(shù)據(jù)等操作

后端如何知道該請(qǐng)求是哪個(gè)?戶發(fā)過(guò)來(lái)的

cookie

session

token

后端的權(quán)限設(shè)計(jì)RBAC

?戶

??

權(quán)限

前端權(quán)限

前端權(quán)限的控制本質(zhì)上來(lái)說(shuō), 就是控制前端的 視圖層的展示和前端所發(fā)送的請(qǐng)求. 但是只有前端權(quán)限控制沒(méi)有后端權(quán)限控制是萬(wàn)萬(wàn)不可的. 前端權(quán)限控制只可以說(shuō)是達(dá)到錦上添花的效果.

1.2.前端權(quán)限的意義

如果僅從能夠修改服務(wù)器中數(shù)據(jù)庫(kù)中的數(shù)據(jù)層?上講,確實(shí)只在后端做控制就?夠了, 那為什么越來(lái)越多的項(xiàng)?也進(jìn)?了前端權(quán)限的控制, 主要有這???的好處

降低?法操作的可能性

不怕賊偷就怕賊惦記, 在??中展示出?個(gè) 就算點(diǎn)擊了也最終會(huì)失敗 的按鈕, 勢(shì)必會(huì)增加有?者?法操作的可能性

盡可能排除不必要請(qǐng)求,減輕服務(wù)器壓?

沒(méi)必要的請(qǐng)求, 操作失敗的請(qǐng)求, 不具備權(quán)限的請(qǐng)求, 應(yīng)該壓根就不需要發(fā)送, 請(qǐng)求少了, ?然也會(huì)減輕服務(wù)器的壓?

提??戶體驗(yàn)

根據(jù)?戶具備的權(quán)限為該?戶展現(xiàn)??權(quán)限范圍內(nèi)的內(nèi)容,避免在界?上給?戶帶來(lái)困擾, 讓?戶專注于分內(nèi)之事

2.前端權(quán)限控制思路

2.1.菜單的控制

在登錄請(qǐng)求中, 會(huì)得到權(quán)限數(shù)據(jù), 當(dāng)然, 這個(gè)需要后端返回?cái)?shù)據(jù)的?持. 前端根據(jù)權(quán)限數(shù)據(jù), 展示對(duì)應(yīng)的菜單.點(diǎn)擊菜單,才能查看相關(guān)的界?.

2.2.界?的控制

如果?戶沒(méi)有登錄,?動(dòng)在地址欄敲?管理界?的地址, 則需要跳轉(zhuǎn)到登錄界?如果?戶已經(jīng)登錄, 可是?動(dòng)敲??權(quán)限內(nèi)的地址, 則需要跳轉(zhuǎn)404界?

2.3.按鈕的控制

在某個(gè)菜單的界?中, 還得根據(jù)權(quán)限數(shù)據(jù), 展示出可進(jìn)?操作的按鈕, ?如刪除,修改,增加

2.4.請(qǐng)求和響應(yīng)的控制

如果?戶通過(guò)?常規(guī)操作, ?如通過(guò)瀏覽器調(diào)試?具將某些禁?的按鈕變成啟?狀態(tài), 此時(shí)發(fā)的請(qǐng)求, 也應(yīng)該被前端所攔截

3. Vue的權(quán)限控制實(shí)現(xiàn)

3.1.菜單的控制

查看登錄之后獲取到的數(shù)據(jù)

{

"data": {

"id": 500,

"rid": 0,

"username": "admin",

"mobile": "13999999999",

"email": "123999@qq.com",

"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1MTI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHmtPsO9r_pxHIQ5i5L1kX9RX444uwnRGaIM"

},

"rights": [{

"id": 125,

"authName": "?戶管理",

"icon": "icon-user",

"children": [{

"id": 110,

"authName": "?戶列表",

"path": "users",

"rights": ["view", "edit", "add", "delete"]

}]

}, {

"id": 103,

"authName": "??管理",

"icon": "icon-tijikongjian",

"children": [{

"id": 111,

"authName": "??列表",

"path": "roles",

"rights": ["view", "edit", "add", "delete"]

}]

}, {

"id": 101,

"authName": "商品管理",

"icon": "icon-shangpin",

"children": [{

"id": 104,

"authName": "商品列表",

"path": "goods",

"rights": ["view", "edit", "add", "delete"]

}, {

"id": 121,

"authName": "商品分類",

"path": "categories",

"rights": ["view", "edit", "add", "delete"]

}]

}],

"meta": {

"msg": "登錄成功",

"status": 200

}

}

在這部分?jǐn)?shù)據(jù)中, 除了該?戶的基本信息之外, 還有兩個(gè)字段很關(guān)鍵

token,?于前端?戶的狀態(tài)保持

rights:該?戶具備的權(quán)限數(shù)據(jù),?級(jí)權(quán)限就對(duì)應(yīng)?級(jí)菜單,?級(jí)權(quán)限就對(duì)應(yīng)?級(jí)菜單

根據(jù)rights中的數(shù)據(jù), 動(dòng)態(tài)渲染左側(cè)菜單欄, 數(shù)據(jù)在Login.vue得到, 但是在Home.vue才使?, 所以可以把數(shù)據(jù)?vuex進(jìn)?維護(hù)

vuex中的代碼

export default new Vuex.Store({

state: {

rightList: []

},

mutations: {

setRightList(state, data) {

state.rightList = data

}

},

actions: {},

getters: {}

})

Login.vue的代碼

login() {

this.$refs.loginFormRef.validate(async valid => {

? ? ? ? ......

? ? ? ? this.$store.commit('setRightList', res.rights)

? ? ? ? this.$message.success('登錄成功')

? ? ? ? this.$router.push('/home')

})

}

Home.vue的代碼

import { mapState } from 'vuex'

computed: { ...mapState(['rightList'])

}

created() {

this.activePath = window.sessionStorage.getItem('activePath')

? ? this.menulist = this.rightList

},

刷新界?菜單消失

原因分析

因?yàn)椴藛螖?shù)據(jù)是登錄之后才獲取到的, 獲取菜單數(shù)據(jù)之后,就存放在Vuex中

?旦刷新界?, Vuex中的數(shù)據(jù)會(huì)重新初始化, 所以會(huì)變成空的數(shù)組

因此, 需要將權(quán)限數(shù)據(jù)存儲(chǔ)在sessionStorage中, 并讓其和Vuex中的數(shù)據(jù)保持同步

代碼解決

export default new Vuex.Store({

state: {

rightList: JSON.parse(sessionStorage.getItem('rightList') || '[]')

},

mutations: {

setRightList(state, data) {

state.rightList = data sessionStorage.setItem('rightList', JSON.stringify(data))

}

},

actions: {},

getters: {}

})

標(biāo)識(shí)?戶名, ?便查看當(dāng)前?戶

vuex的代碼

export default new Vuex.Store({

state: {

rightList: JSON.parse(sessionStorage.getItem('rightList') || '[]'),

username: sessionStorage.getItem('username')

},

mutations: {

setRightList(state, data) {

state.rightList = data sessionStorage.setItem('rightList', JSON.stringify(data))

},

setUsername(state, data) {

state.username = data sessionStorage.setItem('username', data)

}

},

actions: {},

getters: {}

})

Login.vue的代碼

login() {

this.$refs.loginFormRef.validate(async valid => {

? ? ......

? ? this.$store.commit('setRightList', res.rights)

? ? this.$store.commit('setUsername', res.data.username)

? ? this.$message.success('登錄成功')

? ? this.$router.push('/home')

})

}

Home.vue的代碼

computed: { ...mapState(['rightList','username'])}

<el-button type="info" @click="logout">{{username}}退出</el-button>

退出按鈕的邏輯

logout() {

sessionStorage.clear()

? ? this.$router.push('/login')

? ? window.location.reload()

},

3.2.界?的控制

1.正常的邏輯是通過(guò)登錄界?, 登錄成功之后跳轉(zhuǎn)到管理平臺(tái)界?, 但是如果?戶直接敲?管理平臺(tái)的地址, 也是可以跳過(guò)登錄的步驟.所以應(yīng)該在某個(gè)時(shí)機(jī)判斷?戶是否登錄

如何判斷是否登錄

sessionStorage.setItem('token', res.data.token)

什么時(shí)機(jī)

路由導(dǎo)航守衛(wèi)

router.beforeEach((to, from, next) => {

if (to.path === '/login') {

next()

} else {

const token = sessionStorage.getItem('token') if (!token) {

next('/login')

} else {

next()

}

}

})

2.雖然菜單項(xiàng)已經(jīng)被控制住了, 但是路由信息還是完整的存在于瀏覽器,正?如zhangsan這個(gè)?戶并不具備??這個(gè)菜單, 但是他如果??在地址欄中敲?/roles的地址, 依然也可以訪問(wèn)??界?

路由導(dǎo)航守衛(wèi)

路由導(dǎo)航守衛(wèi)固然可以在每次路由地址發(fā)?變化的時(shí)候, 從vuex中取出rightList判斷?戶將要訪問(wèn)的界?, 這個(gè)?戶到底有沒(méi)有權(quán)限.不過(guò)從另外?個(gè)?度來(lái)說(shuō),這個(gè)?戶不具備權(quán)限的路由, 是否也應(yīng)該壓根就不存在呢?

動(dòng)態(tài)路由

登錄成功之后動(dòng)態(tài)添加

App.vue中添加

代碼如下:

router.js

import Vue from 'vue'

import Router from 'vue-router'

import Login from '@/components/Login.vue'

import Home from '@/components/Home.vue'

import Welcome from '@/components/Welcome.vue'

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 NotFound from '@/components/NotFound.vue'

import store from '@/store'

Vue.use(Router) const userRule = {

path: '/users',

component: Users

}

const roleRule = {

path: '/roles',

component: Roles

}

const goodsRule = {

path: '/goods',

component: GoodsList

}

const categoryRule = {

path: '/categories',

component: GoodsCate

}

const ruleMapping = {

'users': userRule,

'roles': roleRule,

'goods': goodsRule,

'categories': categoryRule

}

const router = new Router({

routes: [{

path: '/',

redirect: '/home'

}, {

path: '/login',

component: Login

}, {

path: '/home',

component: Home,

redirect: '/welcome',

children: [{

path: '/welcome',

component: Welcome

},

// { path: '/users', component: Users },

// { path: '/roles', component: Roles },

// { path: '/goods', component: GoodsList },

// { path: '/categories', component: GoodsCate }

]

},

{

path: '*',

component: NotFound

}

]

}) router.beforeEach((to, from, next) => {

if (to.path === '/login') {

next()

} else {

const token = sessionStorage.getItem('token') if (!token) {

next('/login')

} else {

next()

}

}

}) export function initDynamicRoutes() {

const currentRoutes = router.options.routes

const rightList = store.state.rightList rightList.forEach(item => {

item.children.forEach(item => {

currentRoutes[2].children.push(ruleMapping[item.path])

})

}) router.addRoutes(currentRoutes)

}

export default router

Login.vue

import {

initDynamicRoutes

} from '@/router.js'

login() {

this.$refs.loginFormRef.validate(async valid => {

if (!valid) return const {

data: res

} = await this.$http.post('login', this.loginForm)

if (res.meta.status !== 200) return this.$message.error('登錄失??!')

this.$store.commit('setRightList', res.rights)

this.$store.commit('setUsername', res.data.username)

sessionStorage.setItem('token', res.data.token)

initDynamicRoutes()

this.$message.success('登錄成功')

this.$router.push('/home')

})

}

App.vue

import {

initDynamicRoutes

} from '@/router.js'

export default {

name: 'app',

created() {

initDynamicRoutes()

}

}

3.3.按鈕的控制

按鈕控制

雖然?戶可以看到某些界?了, 但是這個(gè)界?的?些按鈕,該?戶可能是沒(méi)有權(quán)限的.因此, 我們需要對(duì)組件中的?些按鈕進(jìn)?控制. ?戶不具備權(quán)限的按鈕就隱藏或者禁?, ?在這塊中, 可以把該邏輯放到?定義指令中

permission.js

import Vue from 'vue'

import router from '@/router.js'

Vue.directive('permission', {

inserted: function(el, binding) {

const action = binding.value.action

const currentRight = router.currentRoute.meta

if (currentRight) {

if (currentRight.indexOf(action) == -1) {

// 不具備權(quán)限

const type = binding.value.effect

if (type === 'disabled') {

el.disabled = true el.classList.add('is-disabled')

} else {

el.parentNode.removeChild(el)

}

}

}

}

})

main.js

import './utils/permission.js'

router.js

export function initDynamicRoutes() {

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)

}

使?指令

v - permission = "{action:'add'}"

v - permission = "{action:'delete', effect:'disabled'}"

3.4.請(qǐng)求和響應(yīng)的控制

請(qǐng)求控制

除了登錄請(qǐng)求都得要帶上token, 這樣服務(wù)器才可以鑒別你的身份

axios.interceptors.request.use(function(req) {

const currentUrl = req.url

if (currentUrl !== 'login') {

req.headers.Authorization = sessionStorage.getItem('token')

}

return req

})

如果發(fā)出了?權(quán)限內(nèi)的請(qǐng)求, 應(yīng)該直接在前端訪問(wèn)內(nèi)組織,雖然這個(gè)請(qǐng)求發(fā)到服務(wù)器也會(huì)被拒絕

import axios from 'axios'

import Vue from 'vue'

import router from '../router'

// 配置請(qǐng)求的跟路徑, ?前?mock模擬數(shù)據(jù), 所以暫時(shí)把這?項(xiàng)注釋起來(lái)

// axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'

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)限

// 查看 get請(qǐng)求?

// 增加 post請(qǐng)求?

// 修改 put請(qǐng)求?

// 刪除 delete請(qǐng)求

const method = req.method

// 根據(jù)請(qǐng)求, 得到是哪種操作

const action = actionMapping[method]

// 判斷action是否存在當(dāng)前路由的權(quán)限中

const rights = router.currentRoute.meta

if (rights && rights.indexOf(action) == -1) {

// 沒(méi)有權(quán)限

alert('沒(méi)有權(quán)限') return Promise.reject(new Error('沒(méi)有權(quán)限'))

}

}

return req

}) axios.interceptors.response.use(function(res) {

return res

}) Vue.prototype.$http = axios

響應(yīng)控制

得到了服務(wù)器返回的狀態(tài)碼401, 代表token超時(shí)或者被篡改了, 此時(shí)應(yīng)該強(qiáng)制跳轉(zhuǎn)到登錄界?

axios.interceptors.response.use(function(res) {

if (res.data.meta.status === 401) {

router.push('/login') sessionStorage.clear() window.location.reload()

}

return res

})

4.?結(jié)

前端權(quán)限的實(shí)現(xiàn)必須要后端提供數(shù)據(jù)?持, 否則?法實(shí)現(xiàn).返回的權(quán)限數(shù)據(jù)的結(jié)構(gòu),前后端需要溝通協(xié)商, 怎樣的數(shù)據(jù)使?起來(lái)才最?便.

4.1.菜單控制

權(quán)限的數(shù)據(jù)需要在多組件之間共享, 因此采?vuex

防?刷新界?,權(quán)限數(shù)據(jù)丟失, 所以需要存儲(chǔ)在sessionStorage, 并且要保證兩者的同步

4.2.界?控制

路由的導(dǎo)航守衛(wèi)可以防?跳過(guò)登錄界?

動(dòng)態(tài)路由可以讓不具備權(quán)限的界?的路由規(guī)則壓根就不存在

4.3.按鈕控制

路由規(guī)則中可以增加路由元數(shù)據(jù)meta

通過(guò)路由對(duì)象可以得到當(dāng)前的路由規(guī)則,以及存儲(chǔ)在此規(guī)則中的meta數(shù)據(jù)

?定義指令可以很?便的實(shí)現(xiàn)按鈕控制

4.4.請(qǐng)求和響應(yīng)控制

請(qǐng)求攔截器和響應(yīng)攔截器的使?

請(qǐng)求?式的約定restful

————————————————

版權(quán)聲明:本文為CSDN博主「前端路啊」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/m0_62118859/article/details/124275448

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容