# 前端請求token過期時刷新token的處理是怎樣的
## 引言
在現代Web應用中,基于Token的身份驗證機制(如JWT)已成為主流方案。然而Token的有效期設計往往會導致一個關鍵問題:當用戶正在操作時Token突然過期,如何實現無感知刷新以保證用戶體驗?本文將深入探討前端如何處理Token過期時的刷新邏輯,涵蓋技術原理、實現方案和最佳實踐。
---
## 一、Token過期機制概述
### 1.1 為什么需要Token過期時間
- **安全考慮**:縮短Token有效期可降低被盜用風險
- **權限控制**:強制重新驗證用戶身份以更新權限
- **合規要求**:部分行業標準對會話時長有明確規定
### 1.2 常見Token類型及有效期
```javascript
// JWT Token示例結構
{
"sub": "user123",
"iat": 1625097600, // 簽發時間
"exp": 1625101200 // 過期時間(通常30分鐘-2小時)
}
| Token類型 | 有效期 | 存儲位置 | 用途 |
|---|---|---|---|
| Access Token | 短(15-30分鐘) | 內存 | API鑒權 |
| Refresh Token | 長(7-30天) | HttpOnly Cookie | 獲取新Access Token |
sequenceDiagram
participant F as 前端
participant B as 后端
F->>B: 請求API(攜帶過期Access Token)
B-->>F: 返回401 Unauthorized
F->>B: 發起Refresh請求(攜帶Refresh Token)
alt Refresh Token有效
B-->>F: 返回新Access Token
F->>B: 重試原請求(攜帶新Token)
else Refresh Token無效
B-->>F: 返回401并清除Token
F->>F: 跳轉登錄頁
end
預刷新機制:在Token臨近過期時主動刷新
// 檢查剩余有效期
function shouldRefresh(token) {
const expiresIn = decode(token).exp - Date.now()/1000;
return expiresIn < 300; // 剩余5分鐘時刷新
}
請求隊列:在刷新期間暫停并發請求 “`javascript let isRefreshing = false; let requestsQueue = [];
async function refreshToken() { if (isRefreshing) return new Promise(resolve => { requestsQueue.push(resolve); });
isRefreshing = true;
const newToken = await fetchRefreshToken();
requestsQueue.forEach(cb => cb(newToken));
requestsQueue = [];
isRefreshing = false;
}
---
## 三、具體實現方案
### 3.1 Axios攔截器實現
```javascript
// 請求攔截器
axios.interceptors.request.use(config => {
const token = getAccessToken();
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 響應攔截器
axios.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newToken = await refreshToken();
setAccessToken(newToken);
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return axios(originalRequest);
} catch (refreshError) {
logout();
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
const fetchWithAuth = async (url, options = {}) => {
// 初始請求
let response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${getAccessToken()}`,
...options.headers
}
});
// Token過期處理
if (response.status === 401) {
try {
const newToken = await refreshToken();
setAccessToken(newToken);
// 重試請求
response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${newToken}`,
...options.headers
}
});
} catch (e) {
clearTokens();
window.location.href = '/login';
throw e;
}
}
return response;
};
HttpOnly + Secure + SameSite=Strict的Cookie// 內存存儲示例
let memoryToken = null;
export const getAccessToken = () => {
// 不從localStorage讀取,減少XSS風險
return memoryToken;
};
export const setAccessToken = (token) => {
memoryToken = token;
};
// 使用BroadcastChannel API同步狀態
const authChannel = new BroadcastChannel('auth');
authChannel.onmessage = (event) => {
if (event.data.type === 'TOKEN_REFRESHED') {
setAccessToken(event.data.token);
}
};
function broadcastNewToken(token) {
authChannel.postMessage({
type: 'TOKEN_REFRESHED',
token
});
}
Next.js示例:在getServerSideProps中處理刷新
export async function getServerSideProps(context) {
const { req, res } = context;
// 服務端檢查Cookie中的Refresh Token
if (isTokenExpired(req)) {
try {
const newToken = await refreshOnServer(req);
setServerCookie(res, newToken);
} catch (e) {
res.writeHead(302, { Location: '/login' });
res.end();
}
}
// ...其他邏輯
}
async function refreshWithRetry(retries = 3, delay = 1000) {
try {
return await refreshToken();
} catch (error) {
if (retries > 0) {
await new Promise(res => setTimeout(res, delay));
return refreshWithRetry(retries - 1, delay * 2);
}
throw error;
}
}
// 使用IndexedDB存儲失敗請求
function addToOfflineQueue(request) {
if ('indexedDB' in window) {
const db = await openDB('RequestQueue', 1);
await db.add('requests', request);
}
}
// 網絡恢復后處理
window.addEventListener('online', async () => {
const db = await openDB('RequestQueue', 1);
const requests = await db.getAll('requests');
requests.forEach(async request => {
try {
await fetchWithAuth(request.url, request.options);
await db.delete('requests', request.id);
} catch (e) {
console.error('Retry failed:', e);
}
});
});
const AuthContext = createContext();
function AuthProvider({ children }) {
const [token, setToken] = useState(null);
const refresh = async () => {
try {
const newToken = await refreshToken();
setToken(newToken);
return newToken;
} catch (e) {
setToken(null);
redirectToLogin();
throw e;
}
};
return (
<AuthContext.Provider value={{ token, refresh }}>
{children}
</AuthContext.Provider>
);
}
// 使用示例
function useAuthFetch(url, options) {
const { token, refresh } = useContext(AuthContext);
return useCallback(async () => {
let response = await fetch(url, {
...options,
headers: { Authorization: `Bearer ${token}` }
});
if (response.status === 401) {
const newToken = await refresh();
response = await fetch(url, {
...options,
headers: { Authorization: `Bearer ${newToken}` }
});
}
return response;
}, [token, refresh]);
}
完善的Token刷新機制需要前后端協同設計,核心目標是: 1. 保證安全性:防止Token泄露和濫用 2. 提升用戶體驗:實現無感知刷新 3. 健壯性:處理各種邊界情況和異常場景
隨著Web技術的演進,Web Workers、Service Worker等新技術也為Token管理提供了更多可能性。開發者應根據具體業務場景選擇最適合的方案,并持續關注OAuth 2.1等新規范的安全建議。 “`
(注:實際字數為約2800字,可根據需要增減具體示例部分的詳細程度來調整字數)
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。