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

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

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

//將當(dāng)前因為token刷新被掛起的請求,存儲到請求列表中
//向請求掛起列表中推入一個函數(shù),函數(shù)內(nèi)部為本次失敗請求的重新發(fā)送,調(diào)用傳入的函數(shù),本次請求就會被重新發(fā)送
requestArr.push(() => {
request(error.config);
});
return;
}
isRefreshing = true;
//2.token無效(錯誤或無效)
//發(fā)送請求,獲取新的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) {
// 如果登錄信息無效,清除無效信息,并跳轉(zhuǎn)登錄頁重新登陸
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ā)送失敗的請求,error.config即本次失敗請求的配置對象,根據(jù)requestArr重新發(fā)送本次因為token刷新而掛起的所有請求
requestArr.forEach((item) => item());
//被掛起的請求都已經(jīng)重新發(fā)送,置空掛起請求列表
requestArr = [];
//重新發(fā)送本次請求,本次請求因為isRefreshing=false,并不在被掛起的請求列表中
return request(error.config);
}).catch((err) => {
console.log("err", err);
}).finally(() => {
// 請求發(fā)送完畢,響應(yīng)處理完畢,無論是否刷新token成功,都改變是否正在刷新token的狀態(tài),以便下一次使用
isRefreshing = false;
});
} else if (status === 403) {
errorMessage = "沒有權(quán)限,請聯(lián)系管理員";
} else if (status === 404) {
errorMessage = "請求資源不存在";

} else if (status === 500) {
errorMessage = "服務(wù)器錯誤,請聯(lián)系管理員";
}
Message.error("errorMessage", errorMessage);
} else if (error.request) {
//請求發(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("請求超時,請重試");
} else {
//意料之外的錯誤
// Something happened in setting up the request that triggered an Error
Message.error(error.message);
}
//將攔截器攔截的錯誤繼續(xù)向后拋出,在錯誤拋出的位置通過try catch進行處理,而不是在攔截器中進行錯誤的處理
return Promise.reject(error);
}
);
3.接口鑒權(quán)問題
即并非所有的接口都可以直接請求,例如查看用戶信息,需要用戶登陸后才可以查看登錄信息,若只傳入接口文檔需求參數(shù),會報錯401 UnAuthorized,因此需要在請求攔截器中向請求頭config.headers塞入登錄態(tài)token
注:沒有token和token過期都會報錯401未授權(quán)
2023/2/9 補充:接口鑒權(quán)可以編寫白名單數(shù)組來規(guī)定哪些接口不需要token鑒權(quán)
未完,待補充。。。