什麼是無感刷新及其實現方式

無感刷新是一種機制,允許前端應用在令牌快要過期時自動請求新的令牌,而不需要用戶的干預。這種方法可以提高用戶體驗,避免因令牌過期導致的登錄中斷。

實現無感刷新的常見方法

Token過期時間管理

  • 當用戶登錄後,前端通常會存儲訪問令牌(如JWT)和刷新令牌。
  • 訪問令牌通常有較短的有效期(如15分鐘),而刷新令牌有效期較長(如7天)。
  • 前端在訪問令牌即將過期時(例如還剩2分鐘時),主動使用刷新令牌向後台請求新的訪問令牌。

攔截器(Interceptor)

  • 前端應用可以使用攔截器(如在Axios或Fetch中)攔截所有請求。
  • 如果檢測到當前訪問令牌已過期,攔截器會先使用刷新令牌獲取新的訪問令牌,再重新發送原始請求。
  • 這樣,用戶感知不到任何中斷,應用也保持了連續的操作。

無感刷新邏輯

  • 設置一個定時器,在令牌即將過期前自動觸發刷新操作。
  • 定期檢查令牌的有效期,如果令牌快要過期則自動刷新,防止用戶操作被中斷。
  • 如果刷新令牌也失效,則將用戶重定向到登錄頁面。

Silent Authentication(靜默認證)

  • 對於部分前端應用,可以使用隱藏的iFrame來進行靜默認證,利用SSO機制在後台完成令牌的刷新,而不影響前台的用戶操作。

Axios 攔截器實現示例

以下是一個完整的Axios攔截器實現的代碼,用於處理JWT令牌的自動刷新和請求重試。在這個示例中,假設你已經有一個後端API可以提供刷新令牌的功能。

1. 創建Axios實例

首先,我們需要創建一個Axios實例,並配置請求攔截器和響應攔截器。

1
import axios from "axios";
2
3
// 創建Axios實例
4
const apiClient = axios.create({
5
baseURL: "https://api.example.com", // 替換為你的API基礎URL
6
timeout: 10000,
7
});

2. 添加請求攔截器

在每個請求中添加Authorization頭部,以便將JWT訪問令牌包含在請求中。

1
// 添加請求攔截器
2
apiClient.interceptors.request.use(
3
(config) => {
4
// 在每個請求中都帶上Authorization頭部
5
const token = localStorage.getItem("token");
6
if (token) {
7
config.headers["Authorization"] = `Bearer ${token}`;
8
}
9
return config;
10
},
11
(error) => {
12
// 處理請求錯誤
13
return Promise.reject(error);
14
},
15
);

3. 添加響應攔截器

處理401錯誤,並在令牌過期時使用刷新令牌獲取新的訪問令牌。

1
// 添加響應攔截器
2
apiClient.interceptors.response.use(
3
(response) => {
4
// 直接返回響應數據
5
return response;
6
},
7
async (error) => {
8
const originalRequest = error.config;
9
10
// 如果響應狀態碼是401且原始請求未被重試過
11
if (
12
error.response &&
13
error.response.status === 401 &&
14
!originalRequest._retry
15
) {
16
originalRequest._retry = true;
17
try {
18
const refreshToken = localStorage.getItem("refreshToken");
19
if (refreshToken) {
20
// 發送刷新令牌請求
21
const { data } = await axios.post(
22
"https://api.example.com/auth/refresh",
23
{ token: refreshToken },
24
);
25
26
// 更新本地存儲的令牌
27
localStorage.setItem("token", data.token);
28
29
// 更新Authorization頭部
30
axios.defaults.headers.common["Authorization"] =
31
`Bearer ${data.token}`;
32
33
// 重新發送原始請求
34
originalRequest.headers["Authorization"] = `Bearer ${data.token}`;
35
return apiClient(originalRequest);
36
}
37
} catch (refreshError) {
38
// 如果刷新令牌也失效,重定向到登錄頁
39
localStorage.removeItem("token");
40
localStorage.removeItem("refreshToken");
41
window.location.href = "/login";
42
}
43
}
44
45
// 處理其他錯誤
46
return Promise.reject(error);
47
},
48
);
49
50
export default apiClient;

代碼解釋

  1. Axios實例化:

    使用axios.create()創建一個自定義的Axios實例apiClient,可以指定基礎URL和其他配置。

  2. 請求攔截器:

    在每個請求發出之前,檢查是否存在JWT訪問令牌,如果存在,則將其添加到請求頭部Authorization中。

  3. 響應攔截器:

    • 處理401錯誤:當伺服器返回401未授權錯誤時,意味著令牌可能已過期。
    • 刷新令牌:如果刷新令牌存在,發送請求到刷新令牌的API端點以獲取新的訪問令牌。
    • 更新令牌:將新的令牌存儲到本地,並更新Axios實例的默認請求頭,以便在後續請求中使用新的令牌。
    • 重試原始請求:使用新的令牌重新發送原始請求。
  4. 刷新令牌失敗:

    如果刷新令牌無效或已過期,清除本地存儲的令牌並將用戶重定向到登錄頁面。

如何使用

在應用中,可以使用apiClient代替原始的Axios實例來發送HTTP請求:

1
import apiClient from "./apiClient";
2
3
async function getUserData() {
4
try {
5
const response = await apiClient.get("/user/profile");
6
console.log(response.data);
7
} catch (error) {
8
console.error("獲取用戶數據失敗:", error);
9
}
10
}

這段代碼通過封裝的apiClient發送請求,自動處理令牌的刷新和重試邏輯。

總結

通過以上方法,可以在前端應用中實現無感刷新,確保用戶在使用過程中不會因令牌過期而被打斷。這種機制提升了用戶體驗,使應用更加流暢。