在使用 axios 进行 HTTP 请求时,需要关注多种安全问题,包括 XSS、CSRF、敏感信息泄露等。
1. XSS(跨站脚本攻击)防护
问题描述
XSS 攻击可能通过 axios 请求的响应数据注入恶意脚本。
防护措施
javascript// 1. 响应数据转义 import DOMPurify from 'dompurify'; axios.interceptors.response.use( (response) => { // 对响应数据进行 XSS 过滤 if (response.data && typeof response.data === 'object') { response.data = sanitizeData(response.data); } return response; } ); function sanitizeData(data) { if (typeof data === 'string') { return DOMPurify.sanitize(data); } if (Array.isArray(data)) { return data.map(sanitizeData); } if (typeof data === 'object' && data !== null) { return Object.keys(data).reduce((acc, key) => { acc[key] = sanitizeData(data[key]); return acc; }, {}); } return data; } // 2. 设置安全的 Content-Type axios.defaults.headers.common['Content-Type'] = 'application/json; charset=utf-8'; // 3. 防止 JSON 注入 axios.interceptors.request.use( (config) => { if (config.data && typeof config.data === 'object') { // 确保发送的是 JSON,不是可执行的 JavaScript config.data = JSON.stringify(config.data); } return config; } );
2. CSRF(跨站请求伪造)防护
问题描述
攻击者诱导用户在已认证的网站上执行非预期的操作。
防护措施
javascript// 1. 使用 CSRF Token const api = axios.create({ xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', withCredentials: true // 允许携带 cookie }); // 2. 手动添加 CSRF Token api.interceptors.request.use((config) => { // 从 meta 标签获取 const token = document.querySelector('meta[name="csrf-token"]')?.content; if (token) { config.headers['X-CSRF-Token'] = token; } // 或从 cookie 获取 const csrfToken = getCookie('csrfToken'); if (csrfToken) { config.headers['X-CSRF-Token'] = csrfToken; } return config; }); // 3. 双重 Cookie 验证 api.interceptors.request.use((config) => { const sessionId = getCookie('sessionId'); const csrfToken = getCookie('csrfToken'); if (sessionId && csrfToken) { config.headers['X-CSRF-Token'] = csrfToken; // 验证 token 和 session 的关联性 } return config; }); // 4. SameSite Cookie 设置 // 服务端设置:Set-Cookie: sessionId=xxx; SameSite=Strict; Secure; HttpOnly
3. 敏感信息保护
Token 安全存储
javascript// ❌ 不要直接存储在 localStorage(XSS 风险) localStorage.setItem('token', token); // ✅ 使用 httpOnly cookie(推荐) // 服务端设置:Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict // ✅ 如果必须使用 localStorage,添加额外的安全措施 const secureStorage = { set(key, value) { // 添加时间戳和签名 const data = { value, timestamp: Date.now(), signature: generateSignature(value) }; localStorage.setItem(key, JSON.stringify(data)); }, get(key) { const item = localStorage.getItem(key); if (!item) return null; try { const data = JSON.parse(item); // 验证签名 if (data.signature !== generateSignature(data.value)) { this.remove(key); return null; } // 检查过期时间(例如 24 小时) if (Date.now() - data.timestamp > 24 * 60 * 60 * 1000) { this.remove(key); return null; } return data.value; } catch { return null; } }, remove(key) { localStorage.removeItem(key); } }; function generateSignature(value) { // 使用简单的哈希或 HMAC return btoa(value + SECRET_KEY); }
请求头中的敏感信息
javascript// 1. 避免在 URL 中传递敏感信息 // ❌ 错误 axios.get(`/api/user?password=${password}`); // ✅ 正确 axios.post('/api/user', { password }); // 2. 请求头加密 import CryptoJS from 'crypto-js'; axios.interceptors.request.use((config) => { // 对敏感头部进行加密 if (config.headers.Authorization) { config.headers['X-Encrypted'] = '1'; // 或使用自定义加密 // config.headers.Authorization = encrypt(config.headers.Authorization); } return config; }); // 3. 限制请求头暴露 // 服务端设置:Access-Control-Expose-Headers: limited-headers
4. HTTPS 和证书验证
javascript// 1. 强制使用 HTTPS const api = axios.create({ baseURL: 'https://api.example.com', // 必须使用 HTTPS }); // 2. Node.js 环境中的证书验证 const https = require('https'); const fs = require('fs'); const api = axios.create({ httpsAgent: new https.Agent({ // 生产环境不要设置为 false rejectUnauthorized: true, // 使用自定义 CA 证书 ca: fs.readFileSync('path/to/ca-cert.pem') }) }); // 3. 证书固定(Certificate Pinning) const httpsAgent = new https.Agent({ checkServerIdentity: (host, cert) => { const expectedFingerprint = 'AA:BB:CC:DD:EE:FF:...'; const actualFingerprint = cert.fingerprint256; if (actualFingerprint !== expectedFingerprint) { throw new Error('Certificate fingerprint mismatch'); } } });
5. 请求参数验证
javascriptimport Joi from 'joi'; // 1. 请求参数校验 const requestSchema = Joi.object({ email: Joi.string().email().required(), password: Joi.string().min(8).max(32).required(), age: Joi.number().integer().min(0).max(150) }); axios.interceptors.request.use((config) => { if (config.data) { const { error } = requestSchema.validate(config.data); if (error) { return Promise.reject(new Error(`Validation error: ${error.message}`)); } } return config; }); // 2. URL 参数编码 axios.interceptors.request.use((config) => { if (config.params) { config.params = Object.keys(config.params).reduce((acc, key) => { acc[key] = encodeURIComponent(config.params[key]); return acc; }, {}); } return config; }); // 3. 防止 SQL 注入(前端层面) function sanitizeInput(input) { if (typeof input !== 'string') return input; // 移除或转义危险字符 return input .replace(/[;\"']/g, '') .replace(/--/g, '') .replace(/\/\*/g, '') .replace(/\*\//g, ''); } axios.interceptors.request.use((config) => { if (config.data) { config.data = Object.keys(config.data).reduce((acc, key) => { acc[key] = sanitizeInput(config.data[key]); return acc; }, {}); } return config; });
6. 响应安全验证
javascript// 1. 验证响应内容类型 axios.interceptors.response.use( (response) => { const contentType = response.headers['content-type']; // 确保响应是 JSON if (!contentType || !contentType.includes('application/json')) { return Promise.reject(new Error('Invalid content type')); } return response; } ); // 2. 验证响应数据签名 axios.interceptors.response.use( (response) => { const signature = response.headers['x-response-signature']; const data = JSON.stringify(response.data); if (signature && !verifySignature(data, signature)) { return Promise.reject(new Error('Invalid response signature')); } return response; } ); function verifySignature(data, signature) { const expectedSignature = CryptoJS.HmacSHA256(data, SECRET_KEY).toString(); return signature === expectedSignature; }
7. 安全头部设置
javascript// 发送安全相关的请求头 const secureApi = axios.create({ headers: { // 防止 MIME 类型嗅探 'X-Content-Type-Options': 'nosniff', // 启用 XSS 过滤器 'X-XSS-Protection': '1; mode=block', // 点击劫持防护 'X-Frame-Options': 'DENY', // 内容安全策略 'Content-Security-Policy': "default-src 'self'", // 严格的传输安全 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains' } });
8. 日志和监控
javascript// 安全日志记录 axios.interceptors.request.use((config) => { // 记录请求日志(不包含敏感信息) securityLogger.info({ type: 'request', url: config.url, method: config.method, timestamp: new Date().toISOString(), // 不要记录 headers 或 data 中的敏感信息 }); return config; }); axios.interceptors.response.use( (response) => { securityLogger.info({ type: 'response', url: response.config.url, status: response.status, timestamp: new Date().toISOString() }); return response; }, (error) => { securityLogger.error({ type: 'error', url: error.config?.url, status: error.response?.status, message: error.message, timestamp: new Date().toISOString() }); return Promise.reject(error); } ); // 异常检测 function detectAnomalies(response) { // 检测异常大的响应 const responseSize = JSON.stringify(response.data).length; if (responseSize > 10 * 1024 * 1024) { // 10MB securityLogger.warn('Unusually large response detected'); } // 检测异常的响应时间 const duration = response.config.metadata?.duration; if (duration > 30000) { // 30秒 securityLogger.warn('Slow response detected'); } }
9. 完整的安全配置示例
javascriptimport axios from 'axios'; import DOMPurify from 'dompurify'; import CryptoJS from 'crypto-js'; const secureApi = axios.create({ baseURL: 'https://api.example.com', timeout: 10000, withCredentials: true, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', headers: { 'Content-Type': 'application/json; charset=utf-8', 'X-Content-Type-Options': 'nosniff', 'X-XSS-Protection': '1; mode=block' } }); // 请求拦截器 - 安全处理 secureApi.interceptors.request.use( (config) => { // 1. 参数验证和清理 if (config.data) { config.data = sanitizeData(config.data); } // 2. 添加请求签名 const timestamp = Date.now(); config.headers['X-Timestamp'] = timestamp; config.headers['X-Signature'] = generateRequestSignature(config, timestamp); // 3. 记录安全日志 logSecurityEvent('request', config); return config; }, (error) => Promise.reject(error) ); // 响应拦截器 - 安全处理 secureApi.interceptors.response.use( (response) => { // 1. 验证响应签名 if (!verifyResponseSignature(response)) { return Promise.reject(new Error('Invalid response signature')); } // 2. XSS 清理 if (response.data) { response.data = sanitizeData(response.data); } // 3. 异常检测 detectAnomalies(response); return response; }, (error) => { logSecurityEvent('error', null, error); return Promise.reject(error); } ); export default secureApi;
安全检查清单
- 使用 HTTPS 传输所有数据
- 启用 CSRF 防护
- 敏感信息使用 httpOnly Cookie 存储
- 对输入数据进行验证和清理
- 对输出数据进行 XSS 过滤
- 设置适当的安全头部
- 实现请求/响应签名验证
- 记录安全日志并监控异常
- 定期更新依赖包
- 进行安全审计和渗透测试