webpack 5 如何用 output.html.csp 为内联脚本与样式生成带哈希的 CSP meta 标签
【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack
如果你的 webpack 构建会产出 HTML 页面,而你想在页面上启用 Content-Security-Policy,又不想维护第三方 CSP 插件,webpack 5 的实验性 HTML 模块支持(experiments.html)提供了output.html.csp选项:它在每个 webpack 产出的 HTML 页面中注入一个<meta http-equiv="Content-Security-Policy">标签,并为页面上每一个内联<script>/<style>附上sha256哈希。本文基于仓库中的 html-csp 示例 和 WebpackOptions.json 中该字段的定义,走一遍从配置到验证的完整路径。
前提条件(均来自上述文档):
- 使用 webpack 5,并启用实验性 HTML 模块:
experiments: { html: true }。 output.html.csp默认为false(什么都不做),只有显式配置后才会生效。- CSP 哈希是在内联之后计算的,因此每个哈希与浏览器实际执行的字节一一对应;这也是为什么通常需要配合
output.html.inline一起使用。
准备示例文件
仓库中的 examples/html-csp 给出了完整的可复现工程。页面结构是一个入口 HTML 加上外部引用的脚本和样式,以及一个原本就写在 HTML 里的内联样式:
入口 src/index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>CSP</title> <!-- 外部样式表:被打包后由 output.html.inline 内联成 <style>,从而被 CSP 哈希 --> <link rel="stylesheet" href="./styles.css" /> <!-- 内联 <style>:原样被哈希 --> <style> body { font-family: sans-serif; } </style> </head> <body> <h1>Content-Security-Policy</h1> <!-- 外部脚本:被打包后内联成 <script> 并被哈希 --> <script src="./app.js"></script> </body> </html>另外两个模块文件:
// src/app.js document.querySelector("h1").classList.add("ready"); console.log("bundled + inlined script, covered by a CSP hash");/* src/styles.css */ h1 { color: #2b3a42; } h1.ready { color: #8ed6fb; }配置 webpack.config.js
关键是output.html下的两个选项配合(完整文件见 examples/html-csp/webpack.config.js):
"use strict"; /** @type {import("../../").Configuration} */ module.exports = { entry: { index: "./src/index.html" }, output: { html: { // 把每个产出的 chunk 内联进页面,产生供 CSP 哈希的内联 <script>/<style> // "script"/"style" 或 RegExp 数组可以收窄内联范围;未内联的部分由 'self' 覆盖 inline: true, // true = 严格基线(script-src/style-src 'self'、object-src 'none'、base-uri 'self') // 外加每个内联脚本/样式一个 sha256;传对象可以加 nonce 或通过 policy 覆盖指令 csp: true } }, experiments: { html: true } };两个选项的作用(依据 WebpackOptions.json 的字段描述与示例 README):
inline: true把每个产出的 chunk 直接内联进 HTML,而不是输出单独的<script src>/<link rel="stylesheet">标签。这样原本外部的<link rel="stylesheet">和<script src>会变成内联的<style>/<script>,与源文件中本来就内联的标签一起成为可哈希对象。inline的其他取值:"script"只内联 JavaScript,"style"只内联 CSS,传入RegExp数组则按 chunk 名匹配。csp: true注入 CSP<meta>,基线为script-src 'self'、style-src 'self'、object-src 'none'、base-uri 'self',并把每个内联<script>/<style>的sha256哈希追加到script-src/style-src。哈希在内联完成后计算,因此与浏览器实际执行的字节一致;仍然留在页面外部的资源由'self'覆盖。
在这个示例目录下用 webpack CLI 运行一次构建(配置即上面这份),即可产出dist/index.html。
验证产出页面
构建成功后(webpack 输出compiled successfully),检查dist/index.html。文档示例的产出页面开头如下(摘自 examples/html-csp/README.md,其中的哈希值是文档示例):
<!DOCTYPE html> <html lang="en"> <head><meta http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-5NlCGoSN8ETSUabbmQKYTt1H6nvYWb5OGZJTsWS8iC8='; style-src 'self' 'sha256-vp73KxgWfPhtZ9IRKOt378FrPIkD/T6FbLXnXfaktI8=' 'sha256-+Ver9ZPpEiV0Cq4WfPc7DL+LUo1ECenMRe/8IigO+wA='; object-src 'none'; base-uri 'self'"> <meta charset="utf-8" /> <title>CSP</title> <!-- 外部样式表:被打包后由 output.html.inline 内联成 <style>,从而被 CSP 哈希 --> <style>/*! ... ***! h1 { color: #2b3a42; } h1.ready { color: #8ed6fb; } </style> <!-- 内联 <style>:原样被哈希 --> <style>/*! ... ***! body { font-family: sans-serif; } </style> </head> <body> <h1>Content-Security-Policy</h1> <!-- 外部脚本:被打包后内联成 <script> 并被哈希 --> <script>/******/ (() => { // webpackBootstrap /*! ... ***! document.querySelector("h1").classList.add("ready"); console.log("bundled + inlined script, covered by a CSP hash"); /******/ })() ;</script> </body> </html>验证要点:
- 页面
<head>中出现生成的<meta http-equiv="Content-Security-Policy">; style-src中出现了两个哈希(对应内联后的外部样式表 + 源文件中原本就内联的<style>),script-src中出现一个哈希(对应内联后的app.jsbundle);- 每个内联
<script>/<style>都能在该 meta 中找到对应的哈希条目。由于哈希按内联后的字节计算,页面内容任何变动都会使旧哈希失配。
README 中还给出了该示例在 Unoptimized 与 Production mode 下两次构建的 stats 输出(同样为文档示例),可用于核对编译是否成功以及哪些资产被产出。
可选分支:nonce 与自定义 policy
把csp: true改为对象即可定制(字段定义见 WebpackOptions.json 中output.html.csp的描述):
hashFunction:内联<script>/<style>的哈希算法,可选sha256、sha384、sha512;nonce:占位 nonce,会加到注入的<script>/<style>标签上并作为'nonce-…'源写入策略。注意它是占位符,需要服务端在每次请求时重写(rewrite it per request server-side);policy:在基线之上合并的 CSP 指令,键是指令名(如"img-src"),值是单个 source 字符串或 source 数组。内联哈希和nonce仍然会被追加到script-src/style-src。
示例(来自 README):
output: { html: { inline: true, csp: { policy: { "img-src": ["'self'", "data:"] } } } }已知限制
- 该功能依附于实验性HTML 模块支持(
experiments.html),不是稳定的通用入口;output.html.inline在 schema 中标注为 5.109.0 加入。 - 页面中已声明的 CSP 不会被触碰:schema 描述明确“Skipped when the page already declares a CSP”,即作者自己在页面里写好的 CSP meta 优先,webpack 跳过注入。
- 只有被内联的脚本/样式才会获得哈希;仍以外链形式存在的资源依赖基线中的
'self',如果你的页面需要从其他域加载脚本或样式,需要通过policy显式扩展对应指令。 - 该机制不依赖任何 CSP 插件,全部由 webpack 在产出 HTML 时完成。
完成配置与验证后,产出页面上就会带有与内联内容逐字节对应的 CSP meta 标签;若后续需要按请求下发真实 nonce,按上面nonce字段说明在服务端做替换即可。
【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考