免費(fèi)試聽(tīng)
您現(xiàn)在的位置: > 人力資源管理師 > 管理系統(tǒng)登錄token小結(jié)登錄時(shí)借助vuex和localSt 管理系統(tǒng)登錄token小結(jié)
登錄時(shí)借助vuex和localStorage進(jìn)行登錄態(tài)的存儲(chǔ)和操作
1.管理系統(tǒng)點(diǎn)擊登錄按鈕,登陸成功后跳轉(zhuǎn)至內(nèi)部后臺(tái)頁(yè)面,需要設(shè)置路由導(dǎo)航守衛(wèi)防止用戶通過(guò)地址欄輸入路由地址的方式跳過(guò)登錄直接進(jìn)入系統(tǒng)后臺(tái),即有門(mén)無(wú)墻
注意:不一定登陸成功后進(jìn)入后臺(tái)就是首頁(yè),可通過(guò)query方式進(jìn)行傳參(未嘗試params,理論上可行且原理相同),保存用戶被導(dǎo)航守衛(wèi)攔截的頁(yè)面,登陸成功后進(jìn)入被攔截的頁(yè)面,以模擬用戶通過(guò)書(shū)簽進(jìn)入系統(tǒng)的場(chǎng)景
// 導(dǎo)航守衛(wèi);
router.beforeEach((to, from, next) => {
//matched數(shù)組中只要有一個(gè)需要驗(yàn)證就進(jìn)行驗(yàn)證
//matched說(shuō)明:/home/user/mobile ,這個(gè)路徑中,matched會(huì)匹配到'/home','/user','/mobile'
if (to.matched.some((record) => record.meta.requiresAuth)) {
//驗(yàn)證vuex中登錄信息user是否存在
if (!store.state.user) {
//未登錄,跳轉(zhuǎn)至登錄頁(yè)
console.log("跳轉(zhuǎn)login");
return next({
name: "login",
query: {
// 將本次路由的fullpath傳遞給login頁(yè)面,fullpath相比path,會(huì)包含頁(yè)面的請(qǐng)求參數(shù)等等信息
redirect: to.fullPath,
},
});
}
next();
} else {
next();
}
});

2.處理重復(fù)請(qǐng)求問(wèn)題 2.1重復(fù)請(qǐng)求登錄接口問(wèn)題
為防止用戶登錄時(shí)頻繁點(diǎn)擊登錄按鈕導(dǎo)致重復(fù)發(fā)送登錄請(qǐng)求且進(jìn)入后臺(tái)頁(yè)面后彈出多個(gè)提示窗口,可通過(guò)elementUI button的loading屬性登錄token無(wú)效,在點(diǎn)擊后將loading綁定的屬性值置為true,使得按鈕被禁用
2.2 重復(fù)請(qǐng)求token刷新問(wèn)題!!!
服務(wù)端返回的登錄token一般都會(huì)設(shè)置有expires,即token的有效時(shí)間,當(dāng)token過(guò)期時(shí),用戶需要重新登錄。但這樣往往是有問(wèn)題的,若用戶在token即將過(guò)期時(shí)在后臺(tái)系統(tǒng)內(nèi)進(jìn)行操作,token過(guò)期讓用戶跳轉(zhuǎn)至登陸頁(yè)面,用戶體驗(yàn)差且可能會(huì)丟失一些重要數(shù)據(jù)的操作,因此這是我們需要進(jìn)行無(wú)感刷新
token過(guò)期后需要更新token,但并不需要每一個(gè)接口都調(diào)用一遍token刷新接口,只要有一個(gè)接口調(diào)用一邊將token更新即可,因此需要一個(gè)標(biāo)識(shí),當(dāng)有接口正在刷新token時(shí),其他接口就沒(méi)必要再繼續(xù)發(fā)送刷新請(qǐng)求。
但此時(shí)又出現(xiàn)了另一個(gè)問(wèn)題,因token過(guò)期而被掛起的請(qǐng)求登錄token無(wú)效,在刷新token后需要重新調(diào)用一次,但因?yàn)樗⑿碌臉?biāo)識(shí)導(dǎo)致只有請(qǐng)求了刷新token接口的請(qǐng)求再刷新了token后被再次調(diào)用,因此還需要一個(gè)數(shù)組,來(lái)記錄因?yàn)閠oken刷新而被掛起的請(qǐng)求。
在記錄被掛起的請(qǐng)求數(shù)組時(shí),采用了一種比較巧妙地方法,直接push一個(gè)函數(shù),函數(shù)內(nèi)部書(shū)寫(xiě)了接口的重新請(qǐng)求,這樣在刷新token后,遍歷調(diào)用一遍被掛起的請(qǐng)求數(shù)組即可
request.js文件
const request = axios.create({
timeout:2000
})
//存儲(chǔ)是否正在更新token的狀態(tài)
let isRefreshing = false;
//存儲(chǔ)因?yàn)榈牡却齮oken刷新而掛起的請(qǐng)求
let requestArr = [];
// 響應(yīng)攔截器
request.interceptors.response.use(
function (response) {
// 狀態(tài)碼2xx 響應(yīng)成功
return response;
},
//錯(cuò)誤處理

function (error) {
// 響應(yīng)失敗
if (error.response) {
//請(qǐng)求發(fā)送成功,響應(yīng)接收完畢,但狀態(tài)碼為失敗
let { status } = error.response;
let errorMessage = "";
if (status === 400) {
errorMessage = "請(qǐng)求參數(shù)錯(cuò)誤";
} else if (status === 401) {
// 無(wú)感刷新,不需要用戶看到token過(guò)期,無(wú)需定義errorMeassage
//1.無(wú)token信息
if (!store.state.user) {
router.push({
name: "login",
query: {
//router.currentRoute就是存儲(chǔ)了路由信息的對(duì)象
redirect: router.currentRoute.fullPath,
},
});
return Promise.reject(error);
}
//檢測(cè)是否已經(jīng)存在了刷新token的請(qǐng)求
if (isRefreshing) {

//將當(dāng)前因?yàn)閠oken刷新被掛起的請(qǐng)求,存儲(chǔ)到請(qǐng)求列表中
//向請(qǐng)求掛起列表中推入一個(gè)函數(shù),函數(shù)內(nèi)部為本次失敗請(qǐng)求的重新發(fā)送,調(diào)用傳入的函數(shù),本次請(qǐng)求就會(huì)被重新發(fā)送
requestArr.push(() => {
request(error.config);
});
return;
}
isRefreshing = true;
//2.token無(wú)效(錯(cuò)誤或無(wú)效)
//發(fā)送請(qǐng)求,獲取新的token
return request({
method: "POST",
url: "/xxx/xxx/refreshtoken",
//qs urlencoded
data: qs.stringify({
refreshtoken: store.state.user.refresh_token,
}),
}).then((res) => {
//刷新token失敗
if (res.data.state !== 1) {
// 如果登錄信息無(wú)效,清除無(wú)效信息,并跳轉(zhuǎn)登錄頁(yè)重新登陸
store.commit("SETUSER", null);
router.push({
name: "login",

query: {
redirect: router.currentRoute.fullPath,
},
});
return Promise.reject(error);
}
//刷新token成功
store.commit("SETUSER", res.data.content);
// 重新發(fā)送失敗的請(qǐng)求,error.config即本次失敗請(qǐng)求的配置對(duì)象,根據(jù)requestArr重新發(fā)送本次因?yàn)閠oken刷新而掛起的所有請(qǐng)求
requestArr.forEach((item) => item());
//被掛起的請(qǐng)求都已經(jīng)重新發(fā)送,置空掛起請(qǐng)求列表
requestArr = [];
//重新發(fā)送本次請(qǐng)求,本次請(qǐng)求因?yàn)閕sRefreshing=false,并不在被掛起的請(qǐng)求列表中
return request(error.config);
}).catch((err) => {
console.log("err", err);
}).finally(() => {
// 請(qǐng)求發(fā)送完畢,響應(yīng)處理完畢,無(wú)論是否刷新token成功,都改變是否正在刷新token的狀態(tài),以便下一次使用
isRefreshing = false;
});
} else if (status === 403) {
errorMessage = "沒(méi)有權(quán)限,請(qǐng)聯(lián)系管理員";
} else if (status === 404) {
errorMessage = "請(qǐng)求資源不存在";

} else if (status === 500) {
errorMessage = "服務(wù)器錯(cuò)誤,請(qǐng)聯(lián)系管理員";
}
Message.error("errorMessage", errorMessage);
} else if (error.request) {
//請(qǐng)求發(fā)送成功,但未收到響應(yīng)
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
// Message.error(error.request);
Message.error("請(qǐng)求超時(shí),請(qǐng)重試");
} else {
//意料之外的錯(cuò)誤
// Something happened in setting up the request that triggered an Error
Message.error(error.message);
}
//將攔截器攔截的錯(cuò)誤繼續(xù)向后拋出,在錯(cuò)誤拋出的位置通過(guò)try catch進(jìn)行處理,而不是在攔截器中進(jìn)行錯(cuò)誤的處理
return Promise.reject(error);
}
);
3.接口鑒權(quán)問(wèn)題
即并非所有的接口都可以直接請(qǐng)求,例如查看用戶信息,需要用戶登陸后才可以查看登錄信息,若只傳入接口文檔需求參數(shù),會(huì)報(bào)錯(cuò)401 UnAuthorized,因此需要在請(qǐng)求攔截器中向請(qǐng)求頭config.headers塞入登錄態(tài)token
注:沒(méi)有token和token過(guò)期都會(huì)報(bào)錯(cuò)401未授權(quán)
2023/2/9 補(bǔ)充:接口鑒權(quán)可以編寫(xiě)白名單數(shù)組來(lái)規(guī)定哪些接口不需要token鑒權(quán)
未完,待補(bǔ)充。。。
名師輔導(dǎo)
環(huán)球網(wǎng)校
建工網(wǎng)校
會(huì)計(jì)網(wǎng)校
新東方
醫(yī)學(xué)教育
中小學(xué)學(xué)歷