您好,登录后才能下订单哦!
# 前端请求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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。