1.權限的分類
視頻學習:https://www.bilibili.com/video/BV15Q4y1K79c/?spm_id_from=333.337.search-card.all.click&vd_source=386b4f5aae076490e1ad9b863a467f37
1.1 后端權限
1. 后端如何知道該請求是哪個用戶發過來的
可以根據 cookie
、session
、token
,一般是根據token
獲取的
2. 后端的權限設計RBAC
權限
一般是分配給角色
,角色
下有很多用戶
,這樣用戶
就有對應的權限
。
一個用戶
可以有多個角色
,一個角色
下有很多用戶
1.2 前端權限
1.必要性
前端權限的控制本質上來說, 就是控制端的視圖層的展示和前端所發送的請求。但是只有前端權限控制沒有后端權限控制是萬萬不可的
。 前端權限控制只可以說是達到錦上添花的效果。
2.好處
為什么越來越多的項目也進行了前端權限的控制, 主要有這幾方面的好處:
- 降低非法操作的可能性,無權限的按鈕可能會帶來有心者非法操作
- 盡可能排除不必要清求, 減輕服務器壓力
- 提高用戶體驗,避免在界面上給用戶帶來困擾, 讓用戶專注于分內之事
2.前端權限控制思路
2.1 菜單的控制
根據后端返回的數據. 前端展示對應的菜單. 點擊菜單, 才能查看相關的界面
2.2 界面的控制
如果用戶沒有登錄, 手動在地址欄敲入管理界面的地址, 則需要跳轉到登錄界面
如果用戶已經登錄, 如果手動敲入非權限內的地址, 則需要跳轉404 界面
2.3 按鈕的控制
在某個菜單的界面中, 還得根據權限數據, 展示出可進行操作的按鈕,比如刪除, 修改, 增加
2.4 請求和響應的控制
如果用戶通過非常規操作, 比如通過瀏覽器調試工具將某些禁用的按鈕變成啟用狀態, 此時發的請求, 也應該被前端所攔截
3.實現步驟
3.1 權限菜單欄控制
3.1.1 登錄按鈕
- 點擊登錄按鈕 ,獲取token和側邊欄數據,將側邊欄數據存入vuex中
- 在home組件中獲取側邊欄數據,渲染到側邊欄
出現的問題:刷新瀏覽器,vuex的數據會被清空
解決:與sessionStorage
結合使用
store
文件下的index.js
:
import Vue from 'vue
import Vuex from 'vuex
Vue .use(Vuex)export default new Vuex.store({state:{rightlist:JsoN.parse(sessionstorage.getItem('rightList')||'[]')},mutations:{setRightList(state, data){state.rightList = datasessionStorage.setItem('rightList',JSON.stringify(data))},...
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
}
3.1.2 登出按鈕
期望:退出后,清空sessionStorage
緩存和vuex
的數據
logout(){// 刪除sessionstorage中的數據sessionStorage.clear()this.$router.push("/login')// 刪除vuex中的數據,讓當前的界面刷新window.location.reload()
}
3.2 界面的控制
3.2.1 登錄成功之后才能跳轉到管理平臺界面。
但是如果用戶直接敲入管理平臺的地址,也是可以跳過登錄的步驟,所以應該在某個時機判斷用戶是否登錄
1)如何判斷是否登錄
緩存中是否有token
sessionStorage.setItem('token', res.data.token)
2)什么時候判斷
路由導航守衛
router.beforeEach((to,from,next)=>{if(to.path ==='/login'){next()} else {const token =sessionstorage.getItem('token')if(!token){next('/login')} else {next()}}
})
3.2.2 具備菜單權限才能跳轉到訪問頁面
雖然菜單項已經被控制住了,但是路由信息還是完整的存在于瀏覽器,正比如zhangsan這個用戶并不具備角色這個菜單,但是他如果自己在地址欄中敲入/roles的地址,依然也可以訪問角色界面
路由導航守衛固然可以在每次路由地址發生變化的時候,從vuex中取出rightlist判斷用戶將要訪問的界面,這個用戶到底有沒有權限。不過從另外一個角度來說,這個用戶不具備權限的路由,是否也應該壓根就不存在呢?
router.js
import Vue from "vue
import Router from 'yue-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'
importGoodsList from '@/components/goods/GoodsList.vue'
import NotFound from '@/components/NotFound.vue'
import store from '@/store1mport'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:'/1ogin',component: Login,},{path:'/home',component:Home,redirect:"/welcomechildren:[{ 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.routesconst rightList =store.state.rightListrightList.forEach(item =>{item.children.forEach(item =>{//item 二級權限const temp = ruleMapping[item.path]//設置路由元信息temp.meta = item.rights//添加動態路由currentRoutes[2].children.push(temp)})})router .addRoutes(currentRoutes)
}export default router
Login.vue
import {initDynamicRoutes } from '@/router.js'login(){this.Srefs.loginFormRef.validate(async valid =>{if(!valid)returnconst { 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.Srouter .push('/home')})
}
問題: 如果我們重新刷新的話動態路由就會消失,動態路由是在登錄成功之后才會調用的,刷新的時候并沒有調用,所以動態路由沒有添加上
解決: 可以在app.vue
中的created
中調用添加動態路由的方法
`App.vue````javascript
import { initDynamicRoutes }from '@/router.js'
export default {name: 'app'created(){initDynamicRoutes()}
}
3.3 按鈕的控制
雖然用戶可以看到某些界面了,但是這個界面的一些按鈕,該用戶可能是沒有權限的.因此,我們需要對組件中的一些按鈕進行控制,用戶不具備權限的按鈕就隱藏或者禁用,而在這塊中,可以把該邏輯放到自定義指令
中
permission.js
自定義指令——顯示還是隱藏按鈕
import Vue from 'vue
import router from '@/router.js'
Vue.directive('permission', {inserted: function(el, binding){const action = binding.value.action//獲取當前路由的metaconst currentRight =router.currentRoute.metaif(currentRight){if(currentRight.indexof(action)==-1){// 不具備權限const type = binding.value.effect//禁用按鈕if(type ==='disabled'){el.disabled = trueel.classList.add('is-disabled')else {//不顯示按鈕el.parentNode.removechild(el)}}}
main.is
引入自定義指令
import'./utils/permission.js'
router.js
router中通過meta獲取按鈕權限
export function initDynamicRoutes(){const currentRoutes =router.options.routesconst rightList =store.state.rightListrightList.forEach(item =>{item.children.forEach(item =>{//下面是核心的兩行,router中加入metaconst itemRule =ruleMapping[item.path]itemRule.meta=item.rightscurrentRoutes[2].children.push(itemRule)})
})
router.addRoutes(currentRoutes)
- 使用指令
v-permission="{action:'add'}"
v-permission="{action:'delete', effect:'disabled'}"
3.4 請求和響應的控制
3.4.1 請求控制
- 除了登錄請求都得要帶上token,這樣服務器才可以鑒別你的身份
axios.interceptors.request.use(function(reg){const currentUrl =reg.ur1if(currenturl !== 'login'){req.headers.Authorization =sessionstorage.getItem('token')}return reg
})
- 如果發出了非權限內的請求,應該直接在前端范圍內組織,雖然這個請求發到服務器也會被拒絕
import axios from'axios'
import Vue from 'vue'
import router from '../router'
//配置請求的跟路徑,目前用mock模擬數據,所以暫時把這一項注釋起來
// 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.urlif(currenturl !== 'login'){req.headers.Authorization =sessionStorage.getItem('token')// 當前模塊中具備的杈限// 查看 get請求// 增加post請求// 修改put請求// 刪除delete請求const method =reg.method// 根據請求,得到是哪種操作const actión= actionMapping[method]// 判斷action是否存在當前路由的權限中const rights =router.currentRoute.metaif(rights && rights.indexof(action)==-1){// 沒有權限alert("沒有權限")return Promise.reject(new Error('沒有權限'))}}return reg
})axios.interceptors.response.use(function(res){return res
})
Vue.prototype.$http =axios
3.4.2 響應控制
- 得到了服務器返回的狀態碼401,代表token超時或者被篡改了,此時應該強制跳轉到登錄界面
axios.interceptors.response.use(function(res){if(res.data.meta.status === 401){router.push('/login')sessionstorage.clear()window.location.reload()}return res
})
4. 小結
前端權限的實現之須要后端提供數據支持, 否則無法實現。
返回的權限數據的結構, 前后端需要溝通協商怎樣的數據便用起來才最方便
4.1 菜單控制
- 權限的數據需要在多組件之間共享, 因此采用
vuex
- 防止刷新界面, 權限數據丟失, 所以需要存在
sessionStorage
, 并目要保證兩者的同步
4.2 界面控制
- 路由的導航守衛可以防止跳過登錄界面
- 動態路由可以讓不具備權限的界面的路由規則壓根就不存在
4.3 按鈕控制
- 路由規則中可以增加路由元數據meta
- 通過路由對象可以得到當前的路由規則以及存在此規則中的meta 數據
- 自定義指令可以很方便的實現按鈕控制
4.4 請求和響應控制
- 請求攔截器和響應攔截器的使用
- 請求方式的約定
restful