這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
什么是JWT
JWT是全稱是JSON WEB TOKEN,是一個開放標準,用于將各方資料資訊作為JSON格式進行物件傳遞,可以對資料進行可選的數字加密,可使用RSA或ECDSA進行公鑰/私鑰簽名,
使用場景
JWT最常見的使用場景就是快取當前用戶登錄資訊,當用戶登錄成功之后,拿到JWT,之后用戶的每一個請求在請求頭攜帶上Authorization欄位來辨別區分請求的用戶資訊,且不需要額外的資源開銷,
相比傳統session的區別
比起傳統的session認證方案,為了讓服務器能識別是哪一個用戶發過來的請求,都需要在服務器上保存一份用戶的登錄資訊(通常保存在記憶體中),再與瀏覽器的cookie打交道,
- 安全方面 由于是使用
cookie來識別用戶資訊的,如果cookie被攔截,用戶會很容易受到跨站請求偽造的攻擊, - 負載均衡 當服務器A保存了用戶A的資料之后,在下一次用戶A服務器A時由于服務器A訪問量較大,被轉發到服務器B,此時服務器B沒有用戶A的資料,會導致
session失效, - 記憶體開銷 隨著時間推移,用戶的增長,服務器需要保存的用戶登錄資訊也就越來越多的,會導致服務器開銷越來越大,
為什么說JWT不需要額外的開銷
JWT為三個部分組成,分別是Header,Payload,Signature,使用.符號分隔,
// 像這樣子 xxxxx.yyyyy.zzzzz
標頭 header
標頭是一個JSON物件,由兩個部分組成,分別是令牌是型別(JWT)和簽名演算法(SHA256,RSA)
{
"alg": "HS256",
"typ": "JWT"
}
負荷 payload
負荷部分也是一個JSON物件,用于存放需要傳遞的資料,例如用戶的資訊
{
"username": "_island",
"age": 18
}
此外,JWT規定了7個可選官方欄位(建議)
| 屬性 | 說明 |
|---|---|
| iss | JWT簽發人 |
| exp | JWT過期時間 |
| sub | JWT面向用戶 |
| aud | JWT接收方 |
| nbf | JWT生效時間 |
| iat | JWT簽發時間 |
| jti | JWT編號 |
簽章 signature
這一部分,是由前面兩個部分的簽名,防止資料被篡改, 在服務器中指定一個密鑰,使用標頭中指定的簽名演算法,按照下面的公式生成這簽名資料
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
在拿到簽名資料之后,把這三個部分的資料拼接起來,每個部分中間使用.來分隔,這樣子我們就生成出一個了JWT資料了,接下來回傳給客戶端儲存起來,而且客戶端在發起請求時,攜帶這個JWT在請求頭中的Authorization欄位,服務器通過解密的方式即可識別出對應的用戶資訊,
JWT優勢和弊端
優勢
- 資料體積小,傳輸速度快
- 無需額外資源開銷來存放資料
- 支持跨域驗證使用
弊端
- 生成出來的
Token無法撤銷,即使重置賬號密碼之前的Token也是可以使用的(需等待JWT過期) - 無法確認用戶已經簽發了多少個
JWT - 不支持
refreshToken
關于refreshToken
refreshToken是Oauth2認證中的一個概念,和accessToken一起生成出來的,
當用戶攜帶的這個accessToken過期時,用戶就需要在重新獲取新的accessToken,而refreshToken就用來重新獲取新的accessToken的憑證,
為什么要有refreshToken
當你第一次接觸的時候,你有沒有一個這樣子的疑惑,為什么需要refreshToken這個東西,而不是服務器端給一個期限較長甚至永久性的accessToken呢?
抱著這個疑惑我在網上搜尋了一番,
其實這個accessToken的使用期限有點像我們生活中的入住酒店,當我們在入住酒店時,會出示我們的身份證明來登記獲取房卡,此時房卡相當于accessToken,可以訪問對應的房間,當你的房卡過期之后就無法再開啟房門了,此時就需要再到前臺更新一下房卡,才能正常進入,這個程序也就相當于refreshToken,
accessToken使用率相比refreshToken頻繁很多,如果按上面所說如果accessToken給定一個較長的有效時間,就會出現不可控的權限泄露風險,
使用refreshToken可以提高安全性
-
用戶在訪問網站時,
accessToken被盜取了,此時攻擊者就可以拿這個accessToke訪問權限以內的功能了,如果accessToken設定一個短暫的有效期2小時,攻擊者能使用被盜取的accessToken的時間最多也就2個小時,除非再通過refreshToken重繪accessToken才能正常訪問, -
設定
accessToken有效期是永久的,用戶在更改密碼之后,之前的accessToken也是有效的
總體來說有了refreshToken可以降低accessToken被盜的風險
關于JWT無感重繪TOKEN方案(結合axios)
業務需求
在用戶登錄應用后,服務器會回傳一組資料,其中就包含了accessToken和refreshToken,每個accessToken都有一個固定的有效期,如果攜帶一個過期的token向服務器請求時,服務器會回傳401的狀態碼來告訴用戶此token過期了,此時就需要用到登錄時回傳的refreshToken呼叫重繪Token的介面(Refresh)來更新下新的token再發送請求即可,
話不多說,先上代碼
工具
axios作為最熱門的http請求庫之一,我們本篇文章就借助它的錯誤回應攔截器來實作token無感重繪功能,
具體實作
本次基于axios-bz代碼片段封裝回應攔截器 可直接配置到你的專案中使用 ?? ??
利用interceptors.response,在業務代碼獲取到介面資料之前進行狀態碼401判斷當前攜帶的accessToken是否失效, 下面是關于interceptors.response中例外階段處理內容,當回應碼為401時,回應攔截器會走中第二個回呼函式onRejected
下面代碼分段可能會讓大家閱讀起來不是很順暢,我直接把整份代碼貼在下面,且每一段代碼之間都添加了對應的注釋
// 最大重發次數
const MAX_ERROR_COUNT = 5;
// 當前重發次數
let currentCount = 0;
// 快取請求佇列
const queue: ((t: string) => any)[] = [];
// 當前是否重繪狀態
let isRefresh = false;
export default async (error: AxiosError<ResponseDataType>) => {
const statusCode = error.response?.status;
const clearAuth = () => {
console.log('身份過期,請重新登錄');
window.location.replace('/login');
// 清空資料
sessionStorage.clear();
return Promise.reject(error);
};
// 為了節省多余的代碼,這里僅展示處理狀態碼為401的情況
if (statusCode === 401) {
// accessToken失效
// 判斷本地是否有快取有refreshToken
const refreshToken = sessionStorage.get('refresh') ?? null;
if (!refreshToken) {
clearAuth();
}
// 提取請求的配置
const { config } = error;
// 判斷是否refresh失敗且狀態碼401,再次進入錯誤攔截器
if (config.url?.includes('refresh')) {
clearAuth();
}
// 判斷當前是否為重繪狀態中(防止多個請求導致多次調refresh介面)
if (!isRefresh) {
// 設定當前狀態為重繪中
isRefresh = true;
// 如果重發次數超過,直接退出登錄
if (currentCount > MAX_ERROR_COUNT) {
clearAuth();
}
// 增加重試次數
currentCount += 1;
try {
const {
data: { access },
} = await UserAuthApi.refreshToken(refreshToken);
// 請求成功,快取新的accessToken
sessionStorage.set('token', access);
// 重置重發次數
currentCount = 0;
// 遍歷佇列,重新發起請求
queue.forEach((cb) => cb(access));
// 回傳請求資料
return ApiInstance.request(error.config);
} catch {
// 重繪token失敗,直接退出登錄
console.log('請重新登錄');
sessionStorage.clear();
window.location.replace('/login');
return Promise.reject(error);
} finally {
// 重置狀態
isRefresh = false;
}
} else {
// 當前正在嘗試重繪token,先回傳一個promise阻塞請求并推進請求串列中
return new Promise((resolve) => {
// 快取網路請求,等token重繪后直接執行
queue.push((newToken: string) => {
Reflect.set(config.headers!, 'authorization', newToken);
// @ts-ignore
resolve(ApiInstance.request<ResponseDataType<any>>(config));
});
});
}
}
return Promise.reject(error);
};
抽離代碼
把上面關于呼叫重繪token的代碼抽離成一個refreshToken函式,單獨處理這一情況,這樣子做有利于提高代碼的可讀性和維護性,且讓看上去代碼不是很臃腫
// refreshToken.ts
export default async function refreshToken(error: AxiosError<ResponseDataType>) {
/*
將上面 if (statusCode === 401) 中的代碼貼進來即可,這里就不重復啦
代碼倉庫地址: https://github.com/QC2168/axios-bz/blob/main/Interceptors/hooks/refreshToken.ts
*/
}
經過上面的邏輯抽離,現在看下攔截器中的代碼就很簡潔了,后續如果要調整相關邏輯直接在refreshToken.ts檔案中調整即可,
import refreshToken from './refreshToken.ts'
export default async (error: AxiosError<ResponseDataType>) => {
const statusCode = error.response?.status;
// 為了節省多余的代碼,這里僅展示處理狀態碼為401的情況
if (statusCode === 401) {
refreshToken()
}
return Promise.reject(error);
};
本文轉載于:
https://juejin.cn/post/7170278285274775560
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/545247.html
標籤:其他
上一篇:nestJs中使用typeORM報'QueryFailedError: Table 'equtype' already exists'錯誤

