子资源完整性(SRI)
子资源完整性(Subresource Integrity, SRI)是一种安全机制,用于确保从 CDN 加载的 JavaScript 和 CSS 文件未被篡改。通过在 <script> 和 <link> 标签上添加 integrity 属性,浏览器会验证文件内容的哈希值是否匹配,防止恶意代码通过 CDN 投毒攻击用户。
前置知识
阅读本节前,建议先了解:表单安全
为什么需要 SRI
当使用第三方 CDN 加载 JavaScript 和 CSS 时,存在以下风险:
- CDN 被入侵:攻击者篡改 CDN 上的文件内容
- 中间人攻击:通过 DNS 劫持等手段替换 CDN 上的文件
- CDN 宕机:虽然 SRI 不解决此问题,但配合 fallback 可用
SRI 工作原理
1. 开发者计算 JS/CSS 文件的哈希值
2. 在 script/link 标签上添加 integrity 属性
3. 浏览器下载文件后计算哈希
4. 对比两个哈希值是否一致
5. 不一致则拒绝执行文件integrity 属性
基本用法
html
<!-- 使用 SRI 的 script 标签 -->
<script
src="https://cdn.example.com/lib.js"
integrity="sha384-abc123..."
crossorigin="anonymous"
></script>
<!-- 使用 SRI 的 link 标签 -->
<link
rel="stylesheet"
href="https://cdn.example.com/styles.css"
integrity="sha384-xyz789..."
crossorigin="anonymous"
>crossorigin 属性
crossorigin 属性必须配合 SRI 使用:
html
<!-- crossorigin="anonymous":发送不带凭据的跨域请求 -->
<script
src="https://cdn.example.com/lib.js"
integrity="sha384-abc123"
crossorigin="anonymous"
></script>crossorigin 是必需的
如果使用 SRI 但不设置 crossorigin,浏览器不会验证文件完整性,SRI 将被忽略。
生成 SRI Hash
命令行生成
bash
# 使用 OpenSSL 生成 SHA-384 哈希
openssl dgst -sha384 -binary lib.js | openssl base64 -A
# 使用 sri-tool
npx sri-tool https://cdn.example.com/lib.js
# 使用 curl + shasum
curl -s https://cdn.example.com/lib.js | shasum -a 384 -b | awk '{print $1}'在线生成
哈希算法选择
| 算法 | 说明 | 推荐度 |
|---|---|---|
sha384 | 384 位 SHA 哈希 | 推荐 |
sha512 | 512 位 SHA 哈希 | 更安全 |
sha256 | 256 位 SHA 哈希 | 兼容性最好 |
Fallback 策略
当 CDN 资源验证失败时,提供本地备用:
html
<!-- SRI 验证失败时回退到本地文件 -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-abc123"
crossorigin="anonymous"></script>
<script>
// 检测外部库是否加载成功
if (typeof MyLibrary === 'undefined') {
// 回退到本地文件
var s = document.createElement('script');
s.src = '/local/lib.js';
document.head.appendChild(s);
}
</script>自动化 SRI 生成
javascript
// Node.js 生成 SRI Hash
const crypto = require('crypto');
const fs = require('fs');
function generateSRIHash(filePath, algorithm = 'sha384') {
const content = fs.readFileSync(filePath);
const hash = crypto.createHash(algorithm).update(content).digest('base64');
return `${algorithm}-${hash}`;
}
// 使用
const hash = generateSRIHash('./lib.min.js');
console.log(hash); // sha384-abc123...完整示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SRI 安全示例</title>
<!-- 带有 SRI 的 CSS -->
<link
rel="stylesheet"
href="https://cdn.example.com/bootstrap/5.3.0/css/bootstrap.min.css"
integrity="sha384-abc123def456..."
crossorigin="anonymous"
/>
<!-- CSP 同时保护 -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' https://cdn.example.com;
">
</head>
<body>
<h1>SRI 安全示例</h1>
<!-- 带有 SRI 的 JavaScript -->
<script
src="https://cdn.example.com/jquery/3.7.1/jquery.min.js"
integrity="sha384-xyz789abc012..."
crossorigin="anonymous"
></script>
<script
src="https://cdn.example.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js"
integrity="sha384-def456abc789..."
crossorigin="anonymous"
></script>
<!-- 本地脚本不需要 SRI -->
<script defer src="/js/app.js"></script>
</body>
</html>注意事项
- crossorigin 是必需的:不设置 crossorigin 会使 SRI 失效
- SRI 仅适用于外部资源:本地文件不需要 SRI
- 哈希算法一致性:integrity 中声明的算法必须与实际计算使用的一致
- 文件更新时重新生成哈希:文件内容变化后需要重新计算 integrity 值
- SRI 不解决 CDN 宕机问题:需要配合 fallback 策略
最佳实践
- 对所有 CDN 加载的 JS 和 CSS 使用 SRI
- 使用 sha384 或 sha512 算法
- 始终配合 crossorigin="anonymous" 使用
- 文件更新后重新生成 integrity 哈希
- 提供 CDN 失效时的 fallback 方案
- 将 SRI 生成集成到构建流程
- 配合 CSP 进一步加强安全
下一节
继续学习:Permissions Policy