You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不依赖Minishlink\WebPush,如何实现Web推送通知发送功能?

项目现状

我正在搭建新闻网站,目标是完全脱离外部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,步骤如下:

  1. 获取订阅用户的endpoint、p256dh、auth详情
  2. 使用ES256(ECDSA)签名手动生成VAPID令牌
  3. 从VAPID私钥和用户公钥派生共享密钥
  4. 通过HKDF生成加密密钥和nonce
  5. 使用AES-128-GCM加密推送payload
  6. 用cURL发送推送请求,携带Authorization: vapid、Encryption、Crypto-Key等必要头信息

但实际结果与预期不符:

  • 预期:浏览器正常接收推送通知
  • 实际:无推送通知,偶尔返回401 Unauthorized或Invalid crypto key错误

已排查JWT生成、密钥派生和加密流程,仍未定位问题,恳请有成功实现经验的人士提供帮助!

内容的提问来源于stack exchange,提问作者Gavriel Adi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 21:28:09