Nginx配置HTTP/2 Server Push时如何为推送资源添加diary_sign签名?

来源:Oracle教程作者:广州网站建设头衔:草根站长
导读:本期聚焦于广州网站建设创作的《Nginx配置HTTP/2 Server Push时如何为推送资源添加diary_sign签名?》,敬请观看详情。HTTP/2 Server Push 在减少页面加载往返上有明显收益,但直接把资源推给客户端也可能引入缓存污染和资源替换风险。若在推送声明中加入 diary_sign 签名,客户端或边缘节点就能快速校验资源来源。本文先用一个基础 nginx.conf 演示 http2_push 指令与 Link 头的触发差异,再给出基于 HMAC-SHA256 的动态签名生成方案,以及如何在代理层校验签名。文中对比固定签名和动态签名两种实现,最后说明签名在 CDN 和浏览器缓存场景中的限制。

HTTP/2 Server Push 并不是一个新鲜特性,但不少项目在配置 Nginx 时只停留在能推送,却很少关注推送声明是否可被校验。diary_sign 可以理解为附加在推送资源列表或 Link 响应头上的一个签名参数,用来证明这份推送列表确实由可信服务端生成,没有经过中间代理篡改。本文会拆开配置过程,从最基础的 push 指令,到动态生成 HMAC 签名,再到代理层校验,覆盖签名落地的关键细节。

Nginx配置HTTP/2 Server Push时如何为推送资源添加diary_sign签名?

先理解两种推送触发方式

Nginx 从 1.13.9 版本开始提供 http2_push 指令,语法非常直观。你可以直接在一个 location 或 server 块中列出希望推送的文件路径,Nginx 会在主请求返回之前把这些资源推给支持 HTTP/2 的客户端。例如:

server {
    listen 443 ssl http2;
    server_name ipipp.com;

    ssl_certificate     /etc/nginx/ssl/example.crt;
    ssl_certificate_key /etc/nginx/ssl/example.key;

    location = / {
        http2_push /css/app.css;
        http2_push /js/app.js;
    }
}

这种配置虽然简单,但它的触发范围比较窄,只对精确匹配的请求生效。实际项目里更常见的做法是通过 Link 响应头触发推送。Nginx 会解析后端返回的 Link 头,发现其中带有 rel=preload 的资源时自动执行推送。比如后端返回:

Link: </css/app.css>; rel=preload; as=style

这行头信息里出现尖括号,是因为 HTTP 规范要求 URI 必须用尖括号包裹。签名参数 diary_sign 可以追加在这个头信息的末尾,写成 Link: </css/app.css>; rel=preload; as=style; diary_sign=xxxx。这样一来,签名就跟着推送声明一起到达客户端或中间网关,不需要额外增加一个响应头。

不过要注意,http2_push 指令和 Link 头是两种触发机制,如果同时配置,Nginx 可能会尝试重复推送。通常建议只保留一种方式,要么纯指令,要么纯响应头。签名方案更适合基于响应头的场景,因为签名可以随 Link 参数直接传递。

生成 diary_sign:固定字符串与 HMAC 的选择

最省事的做法是在配置中写死一个固定签名。例如把签名预先算好,然后以固定字符串形式注入到 Link 头:

location / {
    add_header Link "</css/app.css>; rel=preload; as=style; diary_sign=fixedsign123";
    http2_push /css/app.css;
}

这种固定签名只能用于演示,不能用于生产环境。因为任何人只要看到一次响应,就能知道签名值,之后伪造的推送声明也会被当成合法内容。更安全的方式是基于请求资源路径列表和时间戳计算一个动态签名。签名算法可以选择 HMAC-SHA256,密钥只保存在 Nginx 端,中间代理无法重建。

动态生成签名需要引入 OpenResty 或 Nginx 的 Lua 模块。下面是一段基于 OpenResty 的配置,它在访问阶段用 resty.hmac 计算签名,并把结果写入变量,随后通过 add_header 注入到 Link 头:

location / {
    set $push_sign "";

    access_by_lua_block {
        local resty_hmac = require "resty.hmac"
        local secret = "change-this-secret"
        local path_list = "/css/app.css,/js/app.js"
        local hmac = resty_hmac:new(secret)
        hmac:update(path_list)
        ngx.var.push_sign = ngx.encode_base64(hmac:final())
    }

    add_header Link "</css/app.css>; rel=preload; as=style; diary_sign=$push_sign";
    http2_push /css/app.css;
    http2_push /js/app.js;
}

这里有两个细节需要说明。第一,签名内容只包含资源路径列表,没有包含时间戳,这是为了便于客户端快速验证。如果希望防止重放攻击,可以在 path_list 中追加一个每分钟变化的时间窗口,例如 /css/app.css,/js/app.js,1700000000。第二,http2_push 指令仍然需要显式列出推送文件,否则仅靠 add_header 不会触发实际的 Server Push。

固定签名和动态签名的差异很明显。固定签名配置简单,但密钥不参与运算,一旦暴露就无法恢复信任。动态 HMAC 签名只要密钥不泄露,攻击者就无法伪造合法签名。不过它也带来了一个成本:所有需要推送的路径必须在签名计算前确定,不能随意在 Lua 外部异步修改。

校验 diary_sign 与端侧实现

签名的最终价值在核对阶段体现。对于浏览器端,直接读取 HTTP/2 推送资源的响应头并不容易,因为浏览器不向 JavaScript 暴露 Server Push 的元信息。更实际的校验位置是在网关或服务端代理层,例如用一个 Node.js 服务验证 Nginx 推送出的 Link 头签名是否正确。

下面是一段 Node.js 校验示例,它读取传入的 diary_sign 字段,并用同样的密钥重新计算 HMAC:

const crypto = require('crypto');

function verifyDiarySign(pathList, receivedSign) {
    const secret = 'change-this-secret';
    const expected = crypto
        .createHmac('sha256', secret)
        .update(pathList)
        .digest('base64');
    return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(receivedSign)
    );
}

const pathList = '/css/app.css,/js/app.js';
const receivedSign = 'dGhpcyBpcyBhIGZha2Ugc2lnbmF0dXJl';

if (verifyDiarySign(pathList, receivedSign)) {
    console.log('signature valid');
} else {
    console.log('signature invalid');
}

校验失败时不要直接返回明确的错误信息,以免给攻击者提供调试线索。可以返回一个不带推送声明的普通响应,或者直接按无签名处理。日志中记录失败原因即可。

如果使用 OpenResty 自己做校验,也可以复用同一个 resty.hmac 库。在 header_filter_by_lua_block 阶段解析请求或响应头,再与本地计算结果比较。但要注意,header_filter_by_lua_block 只能读取响应头,无法修改已经发送的推送行为,适合做日志记录或告警。

生产环境中的限制与容错

第一个容易踩坑的地方是路径顺序。签名通常对路径列表做拼接后计算摘要,一旦 Nginx 配置里的 http2_push 顺序与签名计算时的顺序不一致,校验就会失败。建议在配置文件中明确一个数据源,例如用 Nginx 变量或 Lua 脚本统一维护路径列表,避免手动复制两份路径造成漂移。

第二个限制来自 CDN 和反向代理。很多 CDN 会重新打包响应头,甚至合并、删除 Link 头,导致 diary_sign 参数丢失。如果业务链路中经过此类代理,签名方案可能只能覆盖到 CDN 与源站之间,无法到达最终浏览器。此时你可以把签名作为一个自定义头 X-Diary-Sign 单独传递,并在 CDN 上配置保留该头。

第三个需要关注的是浏览器缓存。Server Push 推送的资源如果已经被浏览器缓存,浏览器会发送 RST_STREAM 拒绝推送,这是正常行为,不代表签名有误。排查时不要一看到 Invalid signature 就认为是配置错误,先确认 Link 头是否真的到达校验层,可以通过 Nginx 日志或抓包确认。

最后,签名只解决来源可信问题,不解决内容加密问题。如果资源本身包含敏感数据,仍然需要通过 TLS 保证传输安全。diary_sign 的价值在于防止推送列表被恶意改写,而不是替代 HTTP/2 的传输层安全。理解了这一点,就能在 Nginx 上把 Server Push 从能用到用好。

Nginx HTTP/2推送Server Push签名diary_sign修改时间:2026-10-01 02:36:03

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1001/64051.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。