浏览器只认 RFC 8291 标准的 aes128gcm 加密载荷,服务端不按规范加密,推送直接被丢弃——这不是前端能补救的问题;因为 keys.p256dh 和 keys.auth 是 ECDH 协商所需的公钥材料,非对称密钥,不能直接用作 AES-GCM 密钥,真正加密密钥须由服务端通过 ECDH + HKDF 派生,且前端无权访问私钥或执行合规密钥派生。

浏览器只认 RFC 8291 标准的 aes128gcm 加密载荷,服务端不按规范加密,推送直接被丢弃——这不是前端能补救的问题。
为什么 pushManager.subscribe() 拿到的 keys 不能直接用来 AES 加密
客户端拿到的 keys.p256dh 和 keys.auth 是 ECDH 协商所需的公钥材料,不是对称密钥。你不能拿它们当 AES-GCM 的 key 直接调用 encrypt();那样生成的密文浏览器根本无法解密。
-
p256dh是 P-256 曲线公钥(用于和服务端 VAPID 私钥做 ECDH 密钥派生) -
auth是 16 字节随机 auth secret(参与 HKDF 派生,不可省略) - 真正用于 AES-GCM 加密的 128 位密钥,必须由服务端用 ECDH + HKDF 从这两者+VAPID 私钥中派生出来
- 前端 JS 无权限访问私钥、也无法执行合规的密钥派生逻辑,所以加密必须在服务端完成
web-push 库加密时必须传对三个关键参数
以 Node.js 的 web-push 为例,sendNotification() 内部会自动处理 RFC 8291 加密,但前提是初始化和调用时参数不能错:
- VAPID 私钥必须是 PEM 格式(
-----BEGIN EC PRIVATE KEY-----开头),且对应你注册时传入的公钥 -
subscription对象必须完整包含endpoint、keys.p256dh、keys.auth,缺一不可 - payload 必须是
Uint8Array或Buffer,不能是字符串或 JSON 对象——传字符串会静默失败或触发非标准加密路径 - 若 payload 超过 4096 字节(Chrome 限制),库会自动分片并加 record size header,但需确保
gcmAPIKey(已废弃)不被误设,否则降级为明文推送
浏览器拒绝推送的典型错误信息及定位方式
打开 DevTools → Application → Service Workers → “Update on reload” 打开,然后看 Console 中是否出现:
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
立即学习“前端免费学习笔记(深入)”;
-
Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active service worker:Service Worker 未注册成功,pushManager.subscribe()无处调用 -
DOMException: Registration failed - push service error:服务端返回 HTTP 400/401,重点检查Authorization头中的 VAPID JWT 是否过期、签名是否合法 -
DOMException: InvalidStateError: Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope':Service Worker 里尝试用fetch()发送未加密 payload,违反 Web Push 协议 - 无任何报错但通知不弹出:大概率是 payload 加密格式不符,用
curl -v抓服务端发给 FCM/Autopush 的请求,确认Content-Encoding: aes128gcm和Encryption/Crypto-Key头存在且值可 Base64 解码
本地调试时最容易忽略的兼容性细节
即使代码逻辑全对,以下三点仍会导致本地开发环境完全无法触发加密推送:
- 必须使用 HTTPS(
localhost除外),HTTP 页面调用serviceWorker.register()会直接失败 - Chrome 124+ 默认禁用不安全的推送源,需手动访问
chrome://flags/#unsafely-treat-insecure-origin-as-secure并添加http://your-local.dev(不推荐长期用) - Firefox 要求
userVisibleOnly: true且applicationServerKey非空,漏掉任一字段都会使subscribe()返回空对象而非报错 - 测试用的 VAPID 公钥必须用
urlBase64ToUint8Array()转换,不能直接塞 Base64 字符串——常见错误是没去掉填充符=或混淆了 base64 与 base64url 编码
真正卡住人的从来不是“怎么写加密逻辑”,而是服务端是否严格复现了 RFC 8291 的字节布局:salt 长度必须是 16 字节、AES-GCM 的 tagLength 必须是 128、HKDF 输出密钥长度必须是 16 字节——差一个字节,整个加密体就无效。

















