以下是一个完整的原生 JavaScript AJAX 实现,演示如何将 Token 添加到 Authorization 头部的示例:
基础实现
html复制代码
<!DOCTYPE html>
<html>
<head>
<title>AJAX Token 示例</title>
<script>
// 获取当前用户的 Token(实际应用中从安全存储获取)
function getAuthToken() {
// 实际项目中应从 localStorage/cookie 获取
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
}
// 发送带 Token 的 AJAX 请求
function fetchDataWithToken() {
const xhr = new XMLHttpRequest();
const url = 'https://api.example.com/protected-data';
const token = getAuthToken();
// 配置请求
xhr.open('GET', url, true);
// 设置 Authorization 头部
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
xhr.setRequestHeader('Content-Type', 'application/json');
// 处理响应
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
const status = xhr.status;
if (status >= 200 && status < 300) {
console.log('响应数据:', JSON.parse(xhr.responseText));
document.getElementById('result').innerText =
`请求成功: ${xhr.responseText}`;
} else {
console.error('请求失败:', xhr.statusText);
document.getElementById('result').innerText =
`错误 ${xhr.status}: ${xhr.statusText}`;
}
}
};
// 错误处理
xhr.onerror = function() {
console.error('网络错误');
document.getElementById('result').innerText = '网络请求失败';
};
// 发送请求
xhr.send();
}
</script>
</head>
<body>
<h1>AJAX Token 示例</h1>
<button onclick="fetchDataWithToken()">获取受保护数据</button>
<div id="result" style="margin-top: 20px;"></div>
</body>
</html>
封装为可重用模块
javascript复制代码
// ajaxWithAuth.js
const API_BASE_URL = 'https://api.example.com';
export default {
/**
* 发送带认证的 AJAX 请求
* @param {string} method - HTTP 方法 (GET, POST, PUT, DELETE)
* @param {string} endpoint - API 端点
* @param {object} [data] - 请求数据
* @returns {Promise} 返回 Promise
*/
request(method, endpoint, data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const url = `${API_BASE_URL}${endpoint}`;
const token = this.getAuthToken();
xhr.open(method, url, true);
// 设置头部
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
try {
resolve(JSON.parse(xhr.responseText));
} catch (e) {
resolve(xhr.responseText);
}
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function() {
reject({
status: 0,
statusText: '网络错误'
});
};
xhr.send(data ? JSON.stringify(data) : null);
});
},
// 获取认证 Token
getAuthToken() {
// 实际实现从安全存储获取
return localStorage.getItem('authToken') ||
sessionStorage.getItem('authToken') ||
'';
},
// 封装常用方法
get(endpoint) {
return this.request('GET', endpoint);
},
post(endpoint, data) {
return this.request('POST', endpoint, data);
},
put(endpoint, data) {
return this.request('PUT', endpoint, data);
},
delete(endpoint) {
return this.request('DELETE', endpoint);
}
};
使用示例
html复制代码
<script type="module">
import ajax from './ajaxWithAuth.js';
// 登录示例
async function login() {
try {
const response = await ajax.post('/login', {
username: 'user@example.com',
password: 'securePassword123'
});
// 保存 token 到本地存储
localStorage.setItem('authToken', response.token);
console.log('登录成功');
} catch (error) {
console.error('登录失败:', error);
}
}
// 获取受保护数据
async function fetchProtectedData() {
try {
const userData = await ajax.get('/user/profile');
console.log('用户数据:', userData);
const orders = await ajax.get('/user/orders');
console.log('订单数据:', orders);
} catch (error) {
console.error('请求失败:', error);
// 处理 Token 过期
if (error.status === 401) {
console.log('Token过期,尝试刷新...');
await refreshToken();
return fetchProtectedData(); // 重试请求
}
}
}
// Token 刷新逻辑
async function refreshToken() {
try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await ajax.post('/auth/refresh', { refreshToken });
localStorage.setItem('authToken', response.accessToken);
console.log('Token刷新成功');
} catch (error) {
console.error('刷新Token失败:', error);
logout(); // 刷新失败则登出
}
}
// 登出逻辑
function logout() {
localStorage.removeItem('authToken');
localStorage.removeItem('refreshToken');
console.log('用户已登出');
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
if (localStorage.getItem('authToken')) {
fetchProtectedData();
} else {
document.getElementById('login-section').style.display = 'block';
}
});
</script>
安全增强措施
javascript复制代码
// 安全增强版
const ajaxSecure = {
// ... 基础代码同上 ...
request(method, endpoint, data = null) {
return new Promise((resolve, reject) => {
// ... 同上 ...
// 添加安全头部
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('X-CSRF-Protection', this.getCSRFToken());
// 添加请求时间戳防止缓存
const timestamp = new Date().getTime();
const finalUrl = endpoint.includes('?')
? `${url}&_t=${timestamp}`
: `${url}?_t=${timestamp}`;
xhr.open(method, finalUrl, true);
// ... 其余代码 ...
});
},
// 获取 CSRF Token
getCSRFToken() {
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith('XSRF-TOKEN='))
?.split('=')[1];
return cookieValue || '';
},
// 自动处理 Token 刷新
async requestWithRefresh(method, endpoint, data) {
try {
return await this.request(method, endpoint, data);
} catch (error) {
if (error.status === 401 && !endpoint.includes('/auth/refresh')) {
await this.refreshToken();
return this.request(method, endpoint, data);
}
throw error;
}
},
// 刷新 Token
async refreshToken() {
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) throw new Error('无可用刷新令牌');
try {
const response = await this.request('POST', '/auth/refresh', {
refreshToken
});
this.saveTokens(response);
return true;
} catch (error) {
this.clearTokens();
throw error;
}
},
// 安全保存 Token
saveTokens(authData) {
// 使用安全存储方式
localStorage.setItem('authToken', authData.accessToken);
// 刷新令牌使用 HTTP Only Cookie 存储(由服务器设置)
document.cookie = `refreshToken=${authData.refreshToken}; Secure; HttpOnly; SameSite=Strict; path=/`;
},
// 清除 Token
clearTokens() {
localStorage.removeItem('authToken');
document.cookie = 'refreshToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
}
};
完整流程图
mermaid复制代码导出svg
最佳实践建议
Token 存储安全
javascript复制代码
// 使用加密存储(浏览器扩展) async function secureSetItem(key, value) { if (window.crypto && window.crypto.subtle) { const encrypted = await encryptData(value); localStorage.setItem(key, encrypted); } else { // 回退方案:会话存储 + Base64 sessionStorage.setItem(key, btoa(unescape(encodeURIComponent(value)))); } }
添加请求签名
javascript复制代码
function signRequest(method, url, body) { const timestamp = Date.now(); const nonce = Math.random().toString(36).substring(2, 12); const dataToSign = `${method}|${url}|${timestamp}|${nonce}|${body ? JSON.stringify(body) : ''}`; const hmac = CryptoJS.HmacSHA256(dataToSign, SECRET_KEY); return { 'X-Signature': hmac.toString(CryptoJS.enc.Base64), 'X-Timestamp': timestamp, 'X-Nonce': nonce }; }
双重 Token 验证
javascript复制代码
function setAuthHeaders(xhr) { const token = getAuthToken(); xhr.setRequestHeader('Authorization', `Bearer ${token}`); // 添加设备指纹验证 const deviceId = generateDeviceId(); xhr.setRequestHeader('X-Device-ID', deviceId); } function generateDeviceId() { // 基于浏览器指纹生成唯一ID const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.fillText('ID', 10, 10); return canvas.toDataURL().hashCode(); }
这个实现展示了如何在原生 AJAX 请求中添加认证 Token,并提供了企业级的安全增强措施。实际项目中,建议结合具体框架使用更高级的 HTTP 客户端(如 Axios),但理解底层原理对于处理特殊场景非常重要。