Web Push通知已发送但浏览器未显示问题求助
Web Push Notification 不显示问题排查与修复
直接原因:Service Worker 未触发通知显示
你的 Service Worker 仅监听 push 事件并打印日志,完全没有调用通知显示 API。浏览器不会自动弹出通知,必须在 push 事件处理函数中主动调用 self.registration.showNotification()。
修复后的 Service Worker 代码:
self.addEventListener('push', function(event) { let payload = {}; if (event.data) { try { payload = event.data.json(); } catch (e) { console.error('Payload 解析失败:', e); } } const title = payload.notification?.title || '新通知'; const options = { body: payload.notification?.body || '通知内容', icon: payload.notification?.icon || 'icon.png' }; // 用 waitUntil 保证通知显示操作完成 event.waitUntil( self.registration.showNotification(title, options) ); });
后端加密与请求头的关键错误
虽然请求返回 201,但加密逻辑和请求头存在多处不符合 Web Push 规范的问题,导致浏览器无法解密并处理 payload:
1. 重复的 Content-Type 头
请求头中同时存在 application/octet-stream 和 application/json,这会造成解析冲突。由于发送的是加密后的二进制数据,应保留 application/octet-stream,删除重复的 application/json。
2. 缺失 Encryption 头
必须添加包含盐值的 Encryption 头,否则浏览器无法获取解密所需的盐:
'Encryption: salt=' . $salt, // $salt 为 base64url 编码后的盐值
3. Crypto-Key 头缺失 dh 参数
需要将本地生成的公钥(base64url 编码)作为 dh 参数加入 Crypto-Key 头,用于浏览器端的密钥推导:
'Crypto-Key: p256ecdsa=' . $keys['publicKey'] . ';dh=' . $localJWK->public_to_base64(),
4. 加密内容结构错误
- 盐值处理错误:当前代码将 base64url 编码后的盐值放入内容,但实际需要的是原始二进制盐值。应直接使用
random_bytes(16)生成的原始盐,而非编码后的字符串。 - 缺少 GCM 标签:
openssl_encrypt返回的仅是密文,需要将生成的$tag追加到密文末尾——Web Push 要求 GCM 标签紧跟在密文之后。
修复后的内容构造代码:
// 生成原始二进制盐值 $saltRaw = random_bytes(16); $salt = base64url_encode($saltRaw); // ... 其他加密步骤保持不变 ... $encryptedPayload = openssl_encrypt(json_encode($payload), 'aes-128-gcm', $contentEncryptionKey, OPENSSL_RAW_DATA, $nonce, $tag); // 追加 GCM 标签到密文 $encryptedPayloadWithTag = $encryptedPayload . $tag; // 构造符合规范的加密内容 $content = $saltRaw . pack('N', 4096) . pack('C', mb_strlen($decodedLocalPublicKey, '8bit')) . $decodedLocalPublicKey . $encryptedPayloadWithTag;
5. 修复后的完整请求头
$headers = [ 'TTL: ' . (60*60*12), 'Urgency: normal', 'Content-Type: application/octet-stream', 'Content-Encoding: aes128gcm', 'Content-Length: ' . mb_strlen($content, '8bit'), 'Authorization: WebPush ' . $token, 'Encryption: salt=' . $salt, 'Crypto-Key: p256ecdsa=' . $keys['publicKey'] . ';dh=' . $localJWK->public_to_base64(), ];
额外验证步骤
- 打开浏览器控制台(Application > Service Workers),查看是否有解密失败的错误日志。
- 确认用户已授权通知权限(可通过
Notification.permission检查)。 - 确保
icon.png路径正确,且图片尺寸符合浏览器要求(建议 192x192px)。
内容的提问来源于stack exchange,提问作者DrBrad
相关产品推荐
相关产品推荐

