Web Worker 的安全性是开发者需要重点关注的问题,特别是在处理敏感数据或跨域请求时。
主要安全考虑
1. 同源策略(Same-Origin Policy)
Web Worker 遵循同源策略,只能加载与主页面同源的脚本。
javascript// ❌ 跨域加载 Worker - 会失败 const worker = new Worker('https://malicious-site.com/worker.js'); // ✅ 同源加载 Worker - 正常工作 const worker = new Worker('/workers/worker.js'); // ✅ 使用 Blob URL 绕过(但内容仍需同源) const workerCode = fetch('/trusted-source/worker.js') .then(response => response.text()) .then(code => { const blob = new Blob([code], { type: 'application/javascript' }); return new Worker(URL.createObjectURL(blob)); });
2. 数据隔离
Worker 运行在独立的线程中,无法直接访问主线程的变量和 DOM。
javascript// 主线程 const secretData = 'sensitive information'; const worker = new Worker('worker.js'); // Worker 无法直接访问 secretData // worker.js console.log(secretData); // ReferenceError: secretData is not defined // ✅ 通过消息传递数据(注意数据会被深拷贝) worker.postMessage({ data: secretData });
3. 消息传递安全
使用 postMessage 传递数据时,数据会被结构化克隆。
javascript// ❌ 不安全的消息传递 worker.postMessage({ password: userPassword, token: authToken }); // ✅ 安全的消息传递 - 只传递必要数据 worker.postMessage({ taskId: generateId(), encryptedData: encryptData(data) }); // ✅ 使用 Transferable Objects 避免数据拷贝 const buffer = new ArrayBuffer(1024); worker.postMessage({ buffer }, [buffer]); // buffer 现在为空,数据已转移
4. 防止 XSS 攻击
避免在 Worker 中执行不可信的代码。
javascript// ❌ 危险:执行用户输入的代码 const userCode = getUserInput(); const blob = new Blob([userCode], { type: 'application/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); // ✅ 安全:使用预定义的 Worker 脚本 const worker = new Worker('/trusted-worker.js'); worker.postMessage({ userInput: getUserInput() });
5. CORS 配置
如果需要跨域加载 Worker 资源,需要正确配置 CORS。
javascript// 服务器端需要设置 CORS 头部 // Access-Control-Allow-Origin: https://your-domain.com // Access-Control-Allow-Methods: GET, POST // Access-Control-Allow-Headers: Content-Type // 客户端 const worker = new Worker('https://api.example.com/worker.js');
安全最佳实践
1. 输入验证
javascript// worker.js self.onmessage = function(e) { const data = e.data; // 验证输入数据 if (!isValidInput(data)) { console.error('Invalid input detected'); return; } // 处理数据 const result = processData(data); self.postMessage(result); }; function isValidInput(data) { // 检查数据类型 if (typeof data !== 'object' || data === null) { return false; } // 检查必需字段 if (!data.type || !data.payload) { return false; } // 检查数据大小 if (JSON.stringify(data).length > MAX_SIZE) { return false; } return true; }
2. 错误处理
javascript// 主线程 const worker = new Worker('worker.js'); worker.onerror = function(event) { console.error('Worker error:', event.message); // 记录错误信息(不暴露敏感信息) logError({ message: sanitizeErrorMessage(event.message), filename: event.filename, lineno: event.lineno }); // 根据错误类型采取适当措施 if (isSecurityError(event.error)) { terminateWorker(); } }; function sanitizeErrorMessage(message) { // 移除可能包含敏感信息的部分 return message.replace(/password|token|secret/gi, '[REDACTED]'); }
3. 资源限制
javascript// worker.js const MAX_EXECUTION_TIME = 5000; // 5秒 let startTime = null; self.onmessage = function(e) { startTime = performance.now(); try { const result = processWithTimeout(e.data, MAX_EXECUTION_TIME); self.postMessage(result); } catch (error) { if (error instanceof TimeoutError) { self.postMessage({ error: 'Operation timed out' }); } else { self.postMessage({ error: 'Processing failed' }); } } }; function processWithTimeout(data, timeout) { const result = []; for (let i = 0; i < data.length; i++) { // 检查是否超时 if (performance.now() - startTime > timeout) { throw new TimeoutError(); } result.push(processItem(data[i])); } return result; }
4. 消息加密
javascript// 使用 Web Crypto API 加密敏感数据 async function encryptData(data, key) { const encoder = new TextEncoder(); const encodedData = encoder.encode(data); const iv = crypto.getRandomValues(new Uint8Array(12)); const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, encodedData ); return { encrypted, iv }; } // 主线程 const key = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); const encrypted = await encryptData(sensitiveData, key); worker.postMessage({ encrypted, iv });
5. Worker 池管理
javascriptclass SecureWorkerPool { constructor(workerPath, poolSize = 4) { this.workerPath = workerPath; this.poolSize = poolSize; this.workers = []; this.taskQueue = []; this.init(); } init() { for (let i = 0; i < this.poolSize; i++) { const worker = new Worker(this.workerPath); // 添加错误处理 worker.onerror = (event) => { this.handleWorkerError(event, worker); }; worker.onmessage = (e) => this.handleMessage(e, worker); this.workers.push({ worker, busy: false, id: i }); } } handleWorkerError(event, workerObj) { console.error(`Worker ${workerObj.id} error:`, event.message); // 重启 Worker this.restartWorker(workerObj); } restartWorker(workerObj) { const oldWorker = workerObj.worker; oldWorker.terminate(); const newWorker = new Worker(this.workerPath); newWorker.onerror = (event) => this.handleWorkerError(event, workerObj); newWorker.onmessage = (e) => this.handleMessage(e, workerObj); workerObj.worker = newWorker; workerObj.busy = false; } execute(data) { return new Promise((resolve, reject) => { // 验证输入 if (!this.validateInput(data)) { reject(new Error('Invalid input')); return; } const availableWorker = this.workers.find(w => !w.busy); if (availableWorker) { availableWorker.busy = true; availableWorker.worker.postMessage({ data, resolve, reject }); } else { this.taskQueue.push({ data, resolve, reject }); } }); } validateInput(data) { // 实现输入验证逻辑 return true; } }
Content Security Policy (CSP)
配置 CSP 来限制 Worker 的行为:
html<!-- 在 HTML 头部添加 CSP --> <meta http-equiv="Content-Security-Policy" content="worker-src 'self' https://trusted-domain.com">
javascript// 检查 Worker 是否受 CSP 限制 if (self.isSecureContext) { console.log('Worker is running in secure context'); }
安全检查清单
- 验证所有输入数据
- 使用 HTTPS 加载 Worker 脚本
- 遵循同源策略
- 避免在 Worker 中执行不可信代码
- 实施错误处理和日志记录
- 限制 Worker 执行时间
- 加密敏感数据
- 配置适当的 CSP 策略
- 定期更新 Worker 代码
- 监控 Worker 行为和性能
常见安全漏洞
1. 数据泄露
javascript// ❌ 泄露敏感信息 worker.postMessage({ password: userPassword }); // ✅ 使用加密 const encrypted = await encryptPassword(userPassword); worker.postMessage({ encrypted });
2. 拒绝服务攻击
javascript// ❌ 无限制的处理 worker.postMessage(hugeData); // ✅ 限制数据大小 if (data.length > MAX_SIZE) { throw new Error('Data too large'); }
3. 代码注入
javascript// ❌ 执行用户代码 eval(userCode); // ✅ 使用沙箱环境 const sandbox = createSandbox(); sandbox.execute(userCode);
最佳实践总结
- 输入验证:严格验证所有输入数据
- 错误处理:完善的错误处理机制
- 资源限制:限制执行时间和数据大小
- 数据加密:敏感数据使用加密传输
- HTTPS:使用 HTTPS 加载 Worker 脚本
- CSP 配置:配置适当的内容安全策略
- 定期审计:定期审查 Worker 代码的安全性
- 监控日志:监控 Worker 行为并记录日志