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

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

function (error) {
// 響應失敗
if (error.response) {
//請求發送成功,響應接收完畢,但狀態碼為失敗
let { status } = error.response;
let errorMessage = "";
if (status === 400) {
errorMessage = "請求參數錯誤";
} 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);
}
//檢測是否已經存在了刷新token的請求
if (isRefreshing) {

//將當前因為token刷新被掛起的請求,存儲到請求列表中
//向請求掛起列表中推入一個函數,函數內部為本次失敗請求的重新發送,調用傳入的函數,本次請求就會被重新發送
requestArr.push(() => {
request(error.config);
});
return;
}
isRefreshing = true;
//2.token無效(錯誤或無效)
//發送請求,獲取新的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) {
// 如果登錄信息無效,清除無效信息,并跳轉登錄頁重新登陸
store.commit("SETUSER", null);
router.push({
name: "login",

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

} else if (status === 500) {
errorMessage = "服務器錯誤,請聯系管理員";
}
Message.error("errorMessage", errorMessage);
} else if (error.request) {
//請求發送成功,但未收到響應
// 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);
}
//將攔截器攔截的錯誤繼續向后拋出,在錯誤拋出的位置通過try catch進行處理,而不是在攔截器中進行錯誤的處理
return Promise.reject(error);
}
);
3.接口鑒權問題
即并非所有的接口都可以直接請求,例如查看用戶信息,需要用戶登陸后才可以查看登錄信息,若只傳入接口文檔需求參數,會報錯401 UnAuthorized,因此需要在請求攔截器中向請求頭config.headers塞入登錄態token
注:沒有token和token過期都會報錯401未授權
2023/2/9 補充:接口鑒權可以編寫白名單數組來規定哪些接口不需要token鑒權
未完,待補充。。。