當前位置:成語大全網 - 新華字典 - Vue項目前後端分離下的前端鑒權方案

Vue項目前後端分離下的前端鑒權方案

#?Vue項目前後端分離下的前端鑒權方案

###?技術棧

?前端Vue全家桶,後臺.net。

###?需求分析

?1.?前端路由鑒權,屏蔽地址欄入侵

?2.?路由數據由後臺管理,前端只按固定規則異步加載路由

?3.?權限控制精確到每壹個按鈕

?4.?自動更新token

?5.?同壹個瀏覽器只能登錄壹個賬號

###?前端方案

>?對於需求1、2、3,采用異步加載路由方案

?1.?首先編寫vue全局路由守衛

?2.?排除登錄路由和無需鑒權路由

?3.?登錄後請求拉取用戶菜單數據

?4.?在vuex裏處理菜單和路由匹配數據

?5.?將在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`因此首次打開瀏覽器可能會有登錄已過期的提示,這裏相信大家都能找到適合自己的處理方案

?###?結語

經過這些簡單又好用的處理,壹個基本滿足需求的前後端分離前端鑒權方案就誕生啦