技術棧
前端vue全家桶,后台.net。
需求分析
- 前端路由鑒權,屏蔽地址欄入侵
- 路由數據由后台管理,前端只按固定規則異步加載路由
- 權限控制精確到每一個按鈕
- 自動更新token
- 同一個瀏覽器只能登錄一個賬號
前端方案
對於需求1、2、3,采用異步加載路由方案
- 首先編寫vue全局路由守衛
- 排除登錄路由和無需鑒權路由
- 登錄后請求拉取用戶菜單數據
- 在vuex里處理菜單和路由匹配數據
- 將在vuex里處理好的路由數據通過addRoutes異步推入路由
router.beforeEach((to, from, next) => { // 判斷當前用戶是否已拉取權限菜單 if (store.state.sidebar.userRouter.length === 0) { // 無菜單時拉取 getMenuRouter() .then(res => { let _menu = res.data.Data.ColumnDataList || []; // if (res.data.Data.ColumnDataList.length > 0) { // 整理菜單&路由數據 store.commit("setMenuRouter", _menu); // 推入權限路由列表 router.addRoutes(store.state.sidebar.userRouter); next({...to, replace: true }); // } }) .catch(err => { // console.log(err); // Message.error("服務器連接失敗"); }); } else { //當有用戶權限的時候,說明所有可訪問路由已生成 如訪問沒權限的菜單會自動進入404頁面 if (to.path == "/login") { next({ name: "index" }); } else { next(); } } } else { // 無登錄狀態時重定向至登錄 或可進入無需登錄狀態路徑 if (to.path == "/login" || to.meta.auth === 0) { next(); } else { next({ path: "/login" }); } } });
注意
我這里無需鑒權的路由直接寫在router文件夾下的index.js,通過路由元信息meta攜帶指定標識
{
path: "/err-404", name: "err404", meta: { authentication: false }, component: resolve => require(["../views/error/404.vue"], resolve) },
上面說到路由是根據后台返回菜單數據根據一定規則生成,因此一些不是菜單,又需要登錄狀態的路由,我寫在router文件夾下的router.js里,在上面步驟4里處理后台返回菜單數據時,和處理好的菜單路由數據合並一同通過addRoutes推入。 這樣做會有一定的被地址欄入侵的風險,但是筆者這里大多是不太重要的路由,如果你要求咳咳,可以定一份字典來和后台接口配合精確加載每一個路由。
// 加入企業 { path: "/join-company", name: "join-company", component: resolve => require([`@/views/index/join-company.vue`], resolve) },
在vuex中將分配的菜單數據轉化為前端可用的路由數據,我是這樣做的: 管理系統在新增菜單時需要填寫一個頁面地址字段Url,前端得到后台菜單數據后根據Url字段來匹配路由加載的文件路徑,每個菜單一個文件夾的好處是:你可以在這里拆分js、css和此菜單私有組件等
menu.forEach(item => { let routerItem = { path: item.Url, name: item.Id, meta: { auth: item.Children, }, // 路由元信息 定義路由時即可攜帶的參數,可用來管理每個路由的按鈕操作權限 component: resolve => require([`@/views${item.Url}/index.vue`], resolve) // 路由映射真實視圖路徑 }; routerBox.push(routerItem); });
關於如何精確控制每一個按鈕我是這樣做的,將按鈕編碼放在路由元信息里,在當前路由下匹配來控制頁面上的按鈕是否創建。 菜單數據返回的都是多級結構,每個菜單下的子集就是當前菜單下的按鈕權限碼數組,我把每個菜單下的按鈕放在此菜單的路由元信息meta.auth中。這樣作的好處是:按鈕權限校驗只需匹配每個菜單路由元信息下的數據,這樣校驗池長度通常不會超過5個。
created() {
this.owner = this.$route.meta.auth.map(item => item.Code); } methods: { matchingOwner(auth) { return this.owner.some(item => item === auth); } }
需求4自動更新token,就是簡單的時間判斷,並在請求頭添加字段來通知后台更新token並在頭部返回,前端接受到帶token的請求就直接更新token
// 在axios的請求攔截器中 let token = getSession(auth_code); if (token) config.headers.auth = token; if (tokenIsExpire(token)) { // 判斷是否需要刷新jwt config.headers.refreshtoken = true; } // 在axios的響應攔截器中 if (res.headers.auth) { setSession(auth_code, res.headers.auth); }
對於需求5的處理比較麻煩,要跨tab頁只能通過cookie或local,筆者這里不允許使用cookie因此采用的localstorage。通過打開的新頁面讀取localstorage內的token數據來同步多個頁面的賬號信息。token使用的jwt並前端md5加密。 這里需要注意一點是頁面切換要立即同步賬號信息。
經過需求5改造后的全局路由守衛是這樣的:
function _AUTH_() { // 切換窗口時校驗賬號是否發生變化 window.addEventListener("visibilitychange", function() { let Local_auth = getLocal(auth_code, true); let Session_auth = getSession(auth_code); if (document.hidden == false && Local_auth && Local_auth != Session_auth) { setSession(auth_code, Local_auth, true); router.go(0) } }) router.beforeEach((to, from, next) => { // 判斷當前用戶是否已拉取權限菜單 if (store.state.sidebar.userRouter.length === 0) { // 無菜單時拉取 getMenuRouter() .then(res => { let _menu = res.data.Data.ColumnDataList || []; // if (res.data.Data.ColumnDataList.length > 0) { // 整理菜單&路由數據 store.commit("setMenuRouter", _menu); // 推入權限路由列表 router.addRoutes(store.state.sidebar.userRouter); next({...to, replace: true }); // } }) .catch(err => { // console.log(err); // Message.error("服務器連接失敗"); }); } else { //當有用戶權限的時候,說明所有可訪問路由已生成 如訪問沒權限的菜單會自動進入404頁面 if (to.path == "/login") { next({ name: "index" }); } else { next(); } } } else { // 無登錄狀態時重定向至登錄 或可進入無需登錄狀態路徑 if (to.path == "/login" || to.meta.auth === 0) { next(); } else { next({ path: "/login" }); } } }); }
經過需求5改造后的axios的請求攔截器是這樣的,因為ie無法使用visibilitychange,並且嘗試百度其他屬性無效,因此在請求發出前做了粗暴處理:
if (ie瀏覽器) { setLocal('_ie', Math.random()) let Local_auth = getLocal(auth_code, true); let Session_auth = getSession(auth_code); if (Local_auth && Local_auth != Session_auth) { setSession(auth_code, Local_auth, true); router.go(0) return false } }
這里有一個小問題需要注意:因為用的local因此首次打開瀏覽器可能會有登錄已過期的提示,這里相信大家都能找到適合自己的處理方案
資源搜索網站大全https://55wd.com 廣州品牌設計公司http://www.maiqicn.com
結語
經過這些簡單又好用的處理,一個基本滿足需求的前后端分離前端鑒權方案就誕生啦