Service Worker 不能直接校验 fetch 响应体哈希,须用 response.clone() 复制流并完整读取字节计算 SHA-256;大文件易超时失败,推荐前置校验(如 Blazor 的 integrity 属性交由浏览器原生处理),SW 仅作 fallback。

Service Worker 本身不提供文件哈希校验能力,但可以作为**拦截 + 校验触发器**,把校验逻辑交给客户端 JS 或 WebAssembly 模块执行;真正做 SHA-256 计算的必须是下载后的完整字节流,不能靠 fetch 响应体直接比对——因为响应可能被压缩、分块或代理篡改。
fetch 事件中无法直接校验响应体哈希
很多人误以为在 fetch 事件里拿到 event.response 就能立刻算哈希。实际不行:响应体是 ReadableStream,且一旦读取就会消耗流,后续缓存或返回给页面就失败了。
- 必须用
response.clone()复制一份流用于校验,原流留给缓存或返回 - 校验过程需完整读取字节(
arrayBuffer()或逐块read()),耗时随文件大小线性增长,大文件会卡住 fetch 流程 - 浏览器对 Service Worker 的 CPU 和内存有限制,超时(通常 30–60 秒)会导致整个请求失败,表现为
TypeError: Failed to fetch
校验必须前置到资源加载前,而非响应后
Blazor WebAssembly 的做法更可靠:它把所有启动文件的 SHA-256 哈希写死在 blazor.boot.json(.NET 10+ 改为构建时注入配置),JS 加载器在发起每个 .dll 或 .wasm 请求前,先查清单、生成带 integrity 属性的 <script> 或 <link> 标签,交由浏览器原生校验机制处理。
- 浏览器原生
integrity校验发生在 HTTP 响应头解析后、内容写入缓存前,不经过 JS,更快更稳 - Service Worker 可以监听这些请求,但不应替代它——而是用来 fallback:比如校验失败时自动重试、上报或降级到备用 CDN
- 若必须用 SW 校验(如动态生成的二进制 blob),需确保文件体积 WebAssembly 加速(如
sha256-wasm包)
缓存层需绑定哈希版本,避免脏缓存污染
单纯用 caches.open('v1') 不够。如果服务端悄悄替换了文件但没改 URL,缓存里就存了错误内容。正确做法是把哈希嵌入缓存名或 key:
- 缓存名用哈希衍生:例如
caches.open('cache-' + sha256hash.substring(0,8)) - 或缓存 key 拼接哈希:
cache.put('/app.js?h=abc123', response),后续请求也带相同参数才命中 - 激活阶段清理旧缓存时,不能只删
v1,而要遍历caches.keys(),匹配当前预期哈希前缀再删 - 注意:
cache.addAll()不校验内容,它只保证 URL 列表能全部 fetch 成功;校验必须在 put 单个响应前手动插入
HTTPS + 作用域控制是完整性保护的前提
没有 HTTPS,中间人可直接替换 Service Worker 脚本本身,整个校验链就失效了。localhost 允许调试,但上线必须强制 HTTPS。
- 注册时显式指定
scope,例如navigator.serviceWorker.register('/sw.js', { scope: '/assets/' }),防止 SW 控制无关路径 - 检查
navigator.serviceWorker.controller是否存在且匹配预期脚本 URL,避免旧 SW 长期驻留 - 若发现 controller 不匹配,调用
registration.update()并监听waiting状态,必要时skipWaiting()强制升级
最易被忽略的一点:哈希清单本身也要防篡改。它不能和主应用放同一台服务器且无校验——否则攻击者改了清单再改文件,校验就形同虚设。应把清单哈希硬编码进 HTML 或用子资源完整性(SRI)保护其加载链接。

















