以下是一個完整的原生 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.jsconst 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);});},// 獲取認證 TokengetAuthToken() {// 實際實現從安全存儲獲取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 TokengetCSRFToken() {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;}},// 刷新 Tokenasync 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;}},// 安全保存 TokensaveTokens(authData) {// 使用安全存儲方式localStorage.setItem('authToken', authData.accessToken);// 刷新令牌使用 HTTP Only Cookie 存儲(由服務器設置)document.cookie = `refreshToken=${authData.refreshToken}; Secure; HttpOnly; SameSite=Strict; path=/`;},// 清除 TokenclearTokens() {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 {// 回退方案:會話存儲 + Base64sessionStorage.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() {// 基于瀏覽器指紋生成唯一IDconst canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');ctx.fillText('ID', 10, 10);return canvas.toDataURL().hashCode(); }
這個實現展示了如何在原生 AJAX 請求中添加認證 Token,并提供了企業級的安全增強措施。實際項目中,建議結合具體框架使用更高級的 HTTP 客戶端(如 Axios),但理解底層原理對于處理特殊場景非常重要。