不依赖Minishlink\WebPush,如何实现Web推送通知发送功能?
自定义Web Push实现失败,求替代Minishlink\WebPush的可行方案
项目现状
我正在搭建新闻网站,目标是完全脱离外部Composer包依赖,目前已移除所有第三方包,仅保留Minishlink\WebPush处理推送通知。以下环节已正常完成:
- 生成网站推送所需的VAPID公钥(publicKey)和私钥(privateKey)
- 成功创建并存储订阅用户的endpoint、p256dh和auth密钥
当前使用Minishlink\WebPush的推送代码运行正常,代码如下:
use Minishlink\WebPush\WebPush; use Minishlink\WebPush\Subscription; function sendDeviceNotification($email, $content, $email_subject, $userAvatar, $articlePath) { global $pdo; $query = "SELECT endpoint, p256dh, authKey FROM users_auth_tokens WHERE email = :email"; $stmt = $pdo->prepare($query); $stmt->execute([':email' => $email]); if ($stmt->rowCount() > 0) { $auth = [ 'VAPID' => [ 'subject' => main_url, 'publicKey' => website_push_public_key, 'privateKey' => website_push_private_key, ], ]; $webPush = new WebPush($auth); while ($subscriber = $stmt->fetch(PDO::FETCH_ASSOC)) { $subscription = Subscription::create([ 'endpoint' => $subscriber['endpoint'], 'keys' => [ 'p256dh' => $subscriber['p256dh'], 'auth' => $subscriber['authKey'], ], ]); $payload = json_encode([ 'title' => $content, 'body' => $email_subject, 'icon' => $userAvatar, 'badge' => main_url . '/favicon.ico', 'extraData' => $articlePath, ]); $webPush->queueNotification($subscription, $payload); } foreach ($webPush->flush() as $report) { if ($report->isSuccess()) { } } } }
已验证可用的辅助代码(供参考)
VAPID密钥生成与存储代码
if (strpos(main_url, 'https://') === 0 && empty(website_push_public_key) && empty(website_push_private_key)) { function generateVAPIDKeys() { $config = [ "private_key_type" => OPENSSL_KEYTYPE_EC, "curve_name" => "prime256v1", ]; $res = openssl_pkey_new($config); if (!$res) { throw new Exception('The key pair cannot be created.'); } if (!openssl_pkey_export($res, $privateKeyPem)) { throw new Exception('The private key cannot be extracted.'); } $keyDetails = openssl_pkey_get_details($res); if (!$keyDetails || !isset($keyDetails['ec']['x']) || !isset($keyDetails['ec']['y']) || !isset($keyDetails['ec']['d'])) { throw new Exception('The key details cannot be extracted.'); } $publicKeyHex = '04' . bin2hex($keyDetails['ec']['x']) . bin2hex($keyDetails['ec']['y']); $publicKey = hex2bin($publicKeyHex); $publicKeyBase64 = rtrim(strtr(base64_encode($publicKey), '+/', '-_'), '='); $privateKeyBase64 = rtrim(strtr(base64_encode($keyDetails['ec']['d']), '+/', '-_'), '='); return [ 'publicKey' => $publicKeyBase64, 'privateKey' => $privateKeyBase64, ]; } try { $vapidKeys = generateVAPIDKeys(); $updateKeys = [ 'website_push_public_key' => $vapidKeys['publicKey'], 'website_push_private_key' => $vapidKeys['privateKey'] ]; $sql = "UPDATE system_options SET display = :display WHERE setting_name = :setting_name"; $statement = $pdo->prepare($sql); foreach ($updateKeys as $settingName => $keyValue) { $statement->bindValue(':display', $keyValue, PDO::PARAM_STR); $statement->bindValue(':setting_name', $settingName, PDO::PARAM_STR); $statement->execute(); } } catch (Exception $e) { trigger_error($e->getMessage()); } }
用户订阅密钥存储代码
if (strpos(main_url, 'https://') === 0 && isset($data['push_subscriber']) && !empty($data['push_subscriber'])) { $pushData = $data['push_subscriber']; $endpoint = $pushData['endpoint']; $p256dh = $pushData['keys']['p256dh']; $authKey = $pushData['keys']['auth']; $device_id_decrypted = decrypt($device_id, encryption_key); $sql = "UPDATE users_auth_tokens SET endpoint = :endpoint, p256dh = :p256dh, authKey = :authKey WHERE email = :email AND device_id = :device_id"; $stmt = $pdo->prepare($sql); $stmt->bindParam(':endpoint', $endpoint, PDO::PARAM_STR); $stmt->bindParam(':p256dh', $p256dh, PDO::PARAM_STR); $stmt->bindParam(':authKey', $authKey, PDO::PARAM_STR); $stmt->bindParam(':email', $email, PDO::PARAM_STR); $stmt->bindParam(':device_id', $device_id_decrypted, PDO::PARAM_STR); $stmt->execute(); }
自定义实现尝试与问题
我尝试手动实现Web Push发送逻辑以替代Minishlink\WebPush,步骤如下:
- 获取订阅用户的endpoint、p256dh、auth详情
- 使用ES256(ECDSA)签名手动生成VAPID令牌
- 从VAPID私钥和用户公钥派生共享密钥
- 通过HKDF生成加密密钥和nonce
- 使用AES-128-GCM加密推送payload
- 用cURL发送推送请求,携带
Authorization: vapid、Encryption、Crypto-Key等必要头信息
但实际结果与预期不符:
- 预期:浏览器正常接收推送通知
- 实际:无推送通知,偶尔返回401 Unauthorized或Invalid crypto key错误
已排查JWT生成、密钥派生和加密流程,仍未定位问题,恳请有成功实现经验的人士提供帮助!
内容的提问来源于stack exchange,提问作者Gavriel Adi
相关产品推荐
相关产品推荐

