基本概念
Token的类型
- 访问令牌(Access Token)
- 用于访问受保护资源的凭证
- 具有较短的有效期
- 用于日常API请求认证
- 刷新令牌(Refresh Token)
- 用于获取新的访问令牌
- 具有较长的有效期
- 在Access Token过期时使用
实现原理
基本流程
- 设置拦截器监听请求
- 检测Token是否过期
- 使用Refresh Token获取新的Access Token
- 更新存储的Token
- 重试原始请求
具体实现方案
1. XMLHttpRequest实现
// 创建XMLHttpRequest实例
const xhr = new XMLHttpRequest();
// 登录成功后保存Token
function onLoginSuccess(response) {
localStorage.setItem('accessToken', response.data.accessToken);
localStorage.setItem('refreshToken', response.data.refreshToken);
}
// 发送请求函数
function sendRequest(url, method, data) {
return new Promise((resolve, reject) => {
xhr.open(method, url);
xhr.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('accessToken')}`);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject({ status: xhr.status, response: xhr.responseText });
}
}
};
xhr.send(data ? JSON.stringify(data) : null);
});
}
// 刷新Token函数
async function refreshToken() {
const refreshToken = localStorage.getItem('refreshToken');
try {
const response = await fetch('/api/refresh', {
method: 'POST',
body: JSON.stringify({ refresh_token: refreshToken })
});
const data = await response.json();
if (data.success) {
localStorage.setItem('accessToken', data.newAccessToken);
return true;
}
return false;
} catch(err) {
return false;
}
}
2. Axios实现
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.example.com'
});
// 响应拦截器
apiClient.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshed = await refreshToken();
if (refreshed) {
return apiClient(originalRequest);
}
} catch (refreshError) {
// 处理刷新失败
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
async function refreshToken() {
try {
const response = await apiClient.post('/refresh', {
refreshToken: localStorage.getItem('refreshToken')
});
if (response.data.success) {
const newToken = response.data.accessToken;
localStorage.setItem('accessToken', newToken);
apiClient.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
return true;
}
return false;
} catch (error) {
return false;
}
}
3. Fetch API实现
async function fetchWithToken(url, options = {}) {
let token = localStorage.getItem('accessToken');
const headers = {
...options.headers,
'Authorization': `Bearer ${token}`
};
try {
let response = await fetch(url, { ...options, headers });
if (response.status === 401) {
const refreshed = await refreshToken();
if (refreshed) {
headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`;
return fetch(url, { ...options, headers });
} else {
throw new Error('Token refresh failed');
}
}
return response;
} catch (error) {
throw error;
}
}
4. Uni-app实现
let isRefreshing = false;
let requests = [];
// 响应拦截器
http.interceptor.response((response) => {
if (response.statusCode === 401) {
const retryOriginalRequest = new Promise(resolve => {
requests.push(token => {
response.header.Authorization = `Bearer ${token}`;
resolve(http.request(response.config));
});
});
if (!isRefreshing) {
isRefreshing = true;
refreshToken().then(newToken => {
requests.forEach(callback => callback(newToken));
requests = [];
}).catch(err => {
// 处理刷新失败
uni.navigateTo({ url: '/pages/login/login' });
}).finally(() => {
isRefreshing = false;
});
}
return retryOriginalRequest;
}
return response;
});
安全性考虑
1. Token存储
- 避免存储在localStorage(XSS攻击风险)
- 考虑使用httpOnly cookie
- 加密存储敏感信息
2. 并发请求处理
- 使用请求队列
- 避免重复刷新
- 处理竞态条件
3. 错误处理
- 合理的失败重试机制
- 清晰的错误提示
- 用户友好的重新登录流程
最佳实践建议
- 合理设置Token过期时间
- 实现防重放机制
- 定期清理过期Token
- 监控Token使用情况
- 实现Token吊销机制
这种无感刷新Token的机制可以大大提升用户体验,但需要在安全性和便利性之间找到平衡点。建议根据具体项目需求选择合适的实现方案。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。