静态资源返回 200 却加载失败:排查 SRI 摘要不匹配
脚本地址返回 HTTP 200,加载仍可能被完整性校验拒绝。SRI 将页面里声明的摘要与实际取得的资源字节比较。构建工具或 CDN 即便只改写一个换行符,也会改变摘要。排查时应对照部署清单与客户端实际取得的字节。
1. 本机复现
保存为 integrity_lab.mjs,运行 node integrity_lab.mjs。同一个本地服务返回原始样本和多一个换行的样本,用 Fetch 的 integrity 参数校验。
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { once } from 'node:events';
import { createServer } from 'node:http';
const original = 'console.log("ok");\n';
const changed = original + '\n';
const sri = 'sha384-' + createHash('sha384').update(original).digest('base64');
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/javascript; charset=utf-8' });
res.end(req.url === '/original.js' ? original : changed);
});
server.listen(0, '127.0.0.1');
await once(server, 'listening');
const base = 'http://127.0.0.1:' + server.address().port;
try {
const originalResponse = await fetch(base + '/original.js', { integrity: sri });
assert.equal(await originalResponse.text(), original);
console.log('matching SHA-384:', originalResponse.status);
const changedResponse = await fetch(base + '/changed.js');
assert.equal(changedResponse.status, 200);
assert.equal(await changedResponse.text(), changed);
console.log('changed resource without integrity:', changedResponse.status);
await assert.rejects(fetch(base + '/changed.js', { integrity: sri }), TypeError);
console.log('changed resource with integrity: rejected');
} finally {
server.close();
await once(server, 'close');
}
本地实测输出如下。
matching SHA-384: 200
changed resource without integrity: 200
changed resource with integrity: rejected
2. 把摘要绑定到发布产物
- 在压缩和打包步骤完成后,对最终产物生成
sha384-加 Base64 摘要。HTTP 传输采用内容压缩时,核对的是 Fetch 解码后的资源内容。 - 给产物使用版本化 URL,把该版本的 URL 和摘要一起写入 HTML。发布和回滚时同时核对这两个值,并检查 CDN 是否又做了换行转换、二次压缩混淆或内容注入等字节改写。
- 跨源脚本还要满足 CORS。部署时可以用
crossorigin="anonymous"发起跨源请求,并确认资源服务器返回适当的允许来源响应头。HTTP 200 只能说明请求到了可响应的地址,浏览器还会执行跨域和完整性校验。
小结
对实际发布的资源内容生成摘要,并随资源版本一起更新。先核对客户端拿到的字节,再排查跨源加载条件。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| W3C Subresource Integrity 工作草案 | https://www.w3.org/TR/2026/WD-sri-2-20260320/ | 摘要格式、验证过程和跨源约束 |
| WHATWG Fetch | https://fetch.spec.whatwg.org/#concept-request-integrity-metadata | 请求完整性元数据 |
核验说明
- Windows、Node.js v24.19.0 在回环接口验证 SHA-384 匹配和多一个换行导致的拒绝。浏览器脚本元素、跨源 CORS 与 CDN 部署依据规范说明。