VAPID applicationServerKey 是 Base64URL 编码的 P-256 椭圆曲线公钥(65 字节),用于向推送服务证明应用服务器身份;必须用 secp256r1 生成、提取 raw uncompressed 格式并严格 Base64URL 编码(+→-、/→_、去=),否则 subscribe 会因 InvalidAccessError 失败。

什么是 VAPID applicationServerKey,它为什么必须是 Base64URL 编码的公钥
VAPID(Voluntary Application Server Identification)密钥对用于向推送服务(如 Chrome、Firefox 的 Push API)证明你的应用服务器身份。浏览器只接受 applicationServerKey 为 Base64URL 编码的椭圆曲线公钥(P-256),不是 PEM、不是 DER、也不是普通 Base64 —— 错误编码会导致 Failed to execute 'subscribe' on 'PushManager': InvalidAccessError。
常见错误包括:直接把 public_key.pem 文件内容塞进去、用 base64 命令默认输出带换行和填充的字符串、或混淆了私钥和公钥。
- 必须用
ecdsa-secp256r1(即 P-256)生成密钥对,其他曲线(如 secp384r1)不被支持 - 公钥需提取为
raw uncompressed格式(65 字节),再经 Base64URL 编码(替换+→-、/→_、去掉=) - Node.js 中推荐用
web-push库自动生成:webpush.generateVAPIDKeys(),它返回的对象里publicKey字段已是合规的 Base64URL 字符串
如何在 HTML 页面中安全传入 applicationServerKey 给 Service Worker
不能把密钥硬编码在 HTML 或内联脚本里——这等于公开暴露公钥(虽不致命,但违背最小暴露原则),更严重的是,若后续误将私钥也写进去就彻底崩盘。正确做法是:由后端渲染时注入,或通过独立 JSON 接口异步获取。
最简可控方式是让后端在页面中注入一个全局变量:
立即学习“前端免费学习笔记(深入)”;
<script> window.VAPID_PUBLIC_KEY = 'BH3Z7...XyYQ'; // Base64URL 公钥,无换行无空格 </script>
然后在注册 Service Worker 后调用 pushManager.subscribe() 时传入:
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
navigator.serviceWorker.ready.then(reg => {
reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(window.VAPID_PUBLIC_KEY)
});
});-
urlBase64ToUint8Array()是必需的转换函数,不能跳过;浏览器只认Uint8Array - 别用
atob()直接解 Base64 —— 它不支持 Base64URL,会因-/_报错 - 该函数只需写一次,可封装成工具函数,内容固定(网上有标准实现,注意替换字符后再 pad)
Chrome/Firefox 推送订阅失败的三个典型报错及对应检查点
实际部署时,90% 的订阅失败都卡在这几个环节,按顺序排查效率最高:
-
DOMException: Registration failed - permission denied→ 检查是否在 HTTPS 下运行(localhost 除外),且用户已点击允许通知 -
InvalidStateError: Failed to execute 'subscribe' on 'PushManager': Cannot subscribe to push notifications in a non-secure context→ 当前页面非 HTTPS(HTTP 无效,哪怕本地 IP 地址也不行) -
NetworkError when attempting to fetch resource或静默失败 →applicationServerKey解码后长度不是 65 字节(说明 Base64URL 解码出错或密钥本身非 P-256 公钥)
验证公钥是否合规的最快方式:在控制台执行 urlBase64ToUint8Array('your-key').length === 65,不为 true 就得重生成。
Service Worker 里调用 subscribe 时 userVisibleOnly 必须为 true
这是强制要求,不是建议。设为 false 会导致 Chrome 直接抛 NotSupportedError,Firefox 则可能静默拒绝。
这个字段的意义是:推送消息必须触发系统通知(即用户可见),不允许后台静默接收。所以即使你只想收数据不做 UI,也必须配 userVisibleOnly: true,并在后端发送时带上 notification 字段(哪怕为空对象)。
- 不要尝试绕过它,W3C 规范明确禁止
userVisibleOnly: false - 如果后端用
web-push发送,必须传notification: {}或至少title字段,否则推送服务会拒收 - 某些旧教程写的
gcmAPIKey已废弃,VAPID 下完全不需要
真正容易被忽略的是:密钥生成后没做端到端验证 —— 从生成、编码、注入、解码、订阅、到后端发送,任一环出错都会中断。建议用 curl 模拟一次完整推送链路,比反复刷新页面高效得多。

















