乐闻世界logo
搜索文章和话题

Service Worker 的安全注意事项有哪些?

3月7日 12:06

Service Worker 安全注意事项详解

Service Worker 作为浏览器后台运行的代理服务器,具有强大的能力,同时也带来了一些安全风险。了解这些安全问题对于开发安全的 Web 应用至关重要。

1. HTTPS 要求

为什么必须使用 HTTPS

javascript
// Service Worker 只能在 HTTPS 环境下注册 // 例外:localhost 允许 HTTP if ('serviceWorker' in navigator) { // 检查是否是安全上下文 if (window.isSecureContext) { navigator.serviceWorker.register('/sw.js'); } else { console.error('Service Worker 需要 HTTPS 环境'); } }

安全风险:

  • HTTP 环境下 Service Worker 可能被中间人攻击篡改
  • 攻击者可注入恶意 Service Worker 拦截所有网络请求
  • 用户的敏感数据可能被窃取

检测安全上下文

javascript
// 检查当前环境是否安全 function checkSecureContext() { if (!window.isSecureContext) { console.warn('当前不是安全上下文,Service Worker 功能受限'); return false; } return true; } // 或者检查协议 function isSecureProtocol() { return location.protocol === 'https:' || location.hostname === 'localhost'; }

2. 作用域限制

作用域安全

javascript
// Service Worker 只能控制其作用域内的页面 // 注册时指定作用域 navigator.serviceWorker.register('/sw.js', { scope: '/app/' // 只能控制 /app/ 下的页面 }); // 尝试访问作用域外的资源会失败 // /app/page.html ✅ 可以控制 // /other/page.html ❌ 无法控制

路径遍历防护

javascript
// ❌ 危险:不验证路径可能导致安全问题 self.addEventListener('fetch', event => { const url = new URL(event.request.url); // 恶意请求可能包含 ../ 等路径遍历字符 caches.match(url.pathname); // 危险! }); // ✅ 安全:验证和清理路径 self.addEventListener('fetch', event => { const url = new URL(event.request.url); const pathname = url.pathname; // 验证路径不包含遍历字符 if (pathname.includes('..') || pathname.includes('//')) { event.respondWith(new Response('Invalid path', { status: 400 })); return; } // 只允许访问白名单路径 const allowedPaths = ['/api/', '/assets/', '/static/']; const isAllowed = allowedPaths.some(path => pathname.startsWith(path)); if (!isAllowed) { event.respondWith(new Response('Forbidden', { status: 403 })); return; } event.respondWith(caches.match(event.request)); });

3. 内容安全策略(CSP)

CSP 对 Service Worker 的影响

javascript
// 设置 CSP 防止 XSS 攻击 // 在 HTTP 响应头中设置: // Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' // Service Worker 脚本本身需要符合 CSP // 内联脚本可能被阻止

安全的 Service Worker 脚本加载

javascript
// ✅ 推荐:加载外部脚本 navigator.serviceWorker.register('/sw.js'); // ❌ 避免:使用内联 Service Worker const swCode = ` self.addEventListener('fetch', ...); `; const blob = new Blob([swCode], { type: 'application/javascript' }); const url = URL.createObjectURL(blob); navigator.serviceWorker.register(url); // 可能违反 CSP

4. 缓存安全

敏感数据缓存

javascript
// ❌ 危险:缓存敏感信息 self.addEventListener('fetch', event => { if (event.request.url.includes('/api/user/profile')) { event.respondWith( fetch(event.request).then(response => { // 缓存包含个人信息的响应 caches.open('api-cache').then(cache => { cache.put(event.request, response.clone()); // 危险! }); return response; }) ); } }); // ✅ 安全:不缓存敏感数据 const SENSITIVE_PATHS = [ '/api/auth/', '/api/user/profile', '/api/payment/' ]; self.addEventListener('fetch', event => { const url = new URL(event.request.url); // 检查是否是敏感路径 const isSensitive = SENSITIVE_PATHS.some(path => url.pathname.includes(path) ); if (isSensitive) { // 敏感请求直接走网络,不缓存 event.respondWith(fetch(event.request)); return; } // 非敏感请求可以使用缓存 event.respondWith( caches.match(event.request).then(response => { return response || fetch(event.request); }) ); });

缓存清理安全

javascript
// ✅ 安全的缓存清理 self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { // 只删除当前 Service Worker 创建的缓存 // 避免删除其他应用的缓存 if (cacheName.startsWith('my-app-')) { return caches.delete(cacheName); } }) ); }) ); });

5. 跨站脚本攻击(XSS)防护

防止恶意脚本注入

javascript
// ❌ 危险:直接使用用户输入 self.addEventListener('message', event => { const userInput = event.data.message; // 直接执行用户输入可能导致 XSS eval(userInput); // 极度危险! }); // ✅ 安全:验证和清理输入 self.addEventListener('message', event => { const data = event.data; // 验证消息来源 if (event.origin !== 'https://trusted-domain.com') { console.error('Untrusted origin:', event.origin); return; } // 验证数据类型 if (typeof data.action !== 'string') { return; } // 使用白名单验证操作 const allowedActions = ['skipWaiting', 'claimClients']; if (!allowedActions.includes(data.action)) { console.error('Invalid action:', data.action); return; } // 安全执行 switch (data.action) { case 'skipWaiting': self.skipWaiting(); break; case 'claimClients': self.clients.claim(); break; } });

6. 中间人攻击防护

证书固定(Certificate Pinning)

javascript
// 虽然 Service Worker 无法直接实现证书固定 // 但可以通过检查响应头来增强安全性 self.addEventListener('fetch', event => { event.respondWith( fetch(event.request).then(response => { // 检查安全响应头 const headers = response.headers; // 检查 HSTS 头 if (!headers.get('Strict-Transport-Security')) { console.warn('Missing HSTS header'); } // 检查 CSP 头 if (!headers.get('Content-Security-Policy')) { console.warn('Missing CSP header'); } return response; }) ); });

7. 权限控制

最小权限原则

javascript
// ✅ 只请求必要的权限 // 推送通知权限 async function requestPushPermission() { const permission = await Notification.requestPermission(); return permission === 'granted'; } // 后台同步权限 async function requestSyncPermission() { const registration = await navigator.serviceWorker.ready; if ('sync' in registration) { // 检查权限状态 const status = await navigator.permissions.query({ name: 'periodic-background-sync' }); if (status.state === 'granted') { return true; } } return false; }

8. 更新安全

安全的更新机制

javascript
// ✅ 验证 Service Worker 更新 navigator.serviceWorker.register('/sw.js').then(registration => { registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed') { // 验证新版本的完整性 // 可以通过计算哈希值来验证 verifyServiceWorkerIntegrity(newWorker).then(isValid => { if (isValid) { // 提示用户更新 showUpdateNotification(newWorker); } else { console.error('Service Worker 完整性验证失败'); } }); } }); }); }); // 验证 Service Worker 完整性(示例) async function verifyServiceWorkerIntegrity(worker) { try { const response = await fetch('/sw.js'); const scriptContent = await response.text(); // 这里可以添加哈希验证逻辑 // 比如与服务器返回的哈希值对比 return true; } catch (error) { console.error('验证失败:', error); return false; } }

9. 数据泄露防护

防止缓存泄露

javascript
// ✅ 安全的缓存策略 const PUBLIC_RESOURCES = [ '/', '/index.html', '/styles.css', '/app.js', '/images/' ]; const PRIVATE_RESOURCES = [ '/api/user/', '/api/orders/', '/dashboard/' ]; self.addEventListener('fetch', event => { const url = new URL(event.request.url); // 检查是否是公共资源 const isPublic = PUBLIC_RESOURCES.some(path => url.pathname.startsWith(path) ); // 检查是否是私有资源 const isPrivate = PRIVATE_RESOURCES.some(path => url.pathname.startsWith(path) ); if (isPrivate) { // 私有资源:网络优先,不缓存 event.respondWith( fetch(event.request).catch(() => { return new Response('Network error', { status: 503 }); }) ); } else if (isPublic) { // 公共资源:可以使用缓存 event.respondWith( caches.match(event.request).then(response => { return response || fetch(event.request); }) ); } else { // 未知资源:默认网络请求 event.respondWith(fetch(event.request)); } });

10. 安全最佳实践清单

开发阶段

  • 确保所有环境使用 HTTPS(除 localhost)
  • 合理设置 Service Worker 作用域
  • 不缓存敏感数据(用户信息、支付数据等)
  • 验证所有用户输入
  • 实施 CSP 策略
  • 定期更新 Service Worker

生产环境

  • 监控 Service Worker 异常行为
  • 实施 Subresource Integrity (SRI)
  • 配置 HSTS 头
  • 定期审计缓存内容
  • 实施访问控制
  • 记录安全日志

安全测试建议

javascript
// 安全测试清单 const securityTests = { // 1. 检查 HTTPS checkHTTPS: () => location.protocol === 'https:', // 2. 检查作用域 checkScope: async () => { const registration = await navigator.serviceWorker.ready; console.log('Service Worker scope:', registration.scope); return registration.scope; }, // 3. 检查缓存内容 checkCacheContents: async () => { const cacheNames = await caches.keys(); for (const name of cacheNames) { const cache = await caches.open(name); const requests = await cache.keys(); console.log(`Cache "${name}" contains ${requests.length} items`); } }, // 4. 检查权限 checkPermissions: async () => { const permissions = await navigator.permissions.query({ name: 'notifications' }); console.log('Notification permission:', permissions.state); } };

总结

Service Worker 安全要点:

  1. 必须使用 HTTPS:防止中间人攻击
  2. 合理设置作用域:限制控制能力
  3. 不缓存敏感数据:防止数据泄露
  4. 验证用户输入:防止 XSS 攻击
  5. 实施 CSP:增强内容安全
  6. 定期更新:修复安全漏洞
  7. 最小权限:只请求必要权限
标签:Service Worker