schramltim/web-push

PHP的Web Push库

dev-master 2023-06-26 10:16 UTC

This package is not auto-updated.

Last update: 2024-10-01 14:22:02 UTC


README

PHP的Web Push库

Build Status

WebPush可以用来自动发送通知到服务器端提供Web Push通知的端点,这些通知遵循Web Push协议。由于它已被标准化,因此您无需担心它依赖于哪种服务器类型。

需求

PHP 8.0+ 和以下扩展

  • gmp(可选,但性能更好)
  • mbstring
  • curl
  • openssl

不支持旧版本的PHP版本,但是您可以自由使用以下兼容版本

  • PHP 5.6 或 HHVM: v1.x
  • PHP 7.0: v2.x
  • PHP 7.1: v3.x-v5.x
  • PHP 7.2: v6.x
  • PHP 7.3 7.4: v7.x

本README仅与最新版本兼容。每个库版本都有一个git标签,可以通过该标签读取相应的README。

安装

使用 composer 下载并安装库及其依赖项。

composer require minishlink/web-push

使用

<?php

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

// array of notifications
$notifications = [
    [
        'subscription' => Subscription::create([
            'endpoint' => 'https://updates.push.services.mozilla.com/push/abc...', // Firefox 43+,
            'publicKey' => 'BPcMbnWQL5GOYX/5LKZXT6sLmHiMsJSiEvIFvfcDvX7IZ9qqtq68onpTPEYmyxSQNiH7UD/98AUcQ12kBoxz/0s=', // base 64 encoded, should be 88 chars
            'authToken' => 'CxVX6QsVToEGEcjfYPqXQw==', // base 64 encoded, should be 24 chars
        ]),
        'payload' => 'hello !',
    ], [
        'subscription' => Subscription::create([
            'endpoint' => 'https://fcm.googleapis.com/fcm/send/abcdef...', // Chrome
        ]),
        'payload' => null,
    ], [
        'subscription' => Subscription::create([
            'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
            'publicKey' => '(stringOf88Chars)',
            'authToken' => '(stringOf24Chars)',
            'contentEncoding' => 'aesgcm', // one of PushManager.supportedContentEncodings
        ]),
        'payload' => '{msg:"test"}',
    ], [
          'subscription' => Subscription::create([ // this is the structure for the working draft from october 2018 (https://www.w3.org/TR/2018/WD-push-api-20181026/) 
              "endpoint" => "https://example.com/other/endpoint/of/another/vendor/abcdef...",
              "keys" => [
                  'p256dh' => '(stringOf88Chars)',
                  'auth' => '(stringOf24Chars)'
              ],
          ]),
          'payload' => '{"msg":"Hello World!"}',
      ],
];

$webPush = new WebPush();

// send multiple notifications with payload
foreach ($notifications as $notification) {
    $webPush->queueNotification(
        $notification['subscription'],
        $notification['payload'] // optional (defaults null)
    );
}

/**
 * Check sent results
 * @var MessageSentReport $report
 */
foreach ($webPush->flush() as $report) {
    $endpoint = $report->getRequest()->getUri()->__toString();

    if ($report->isSuccess()) {
        echo "[v] Message sent successfully for subscription {$endpoint}.";
    } else {
        echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
    }
}

/**
 * send one notification and flush directly
 * @var MessageSentReport $report
 */
$report = $webPush->sendOneNotification(
    $notifications[0]['subscription'],
    $notifications[0]['payload'] // optional (defaults null)
);

Web Push实现的全示例

身份验证(VAPID)

浏览器需要验证您的身份。一个名为VAPID的标准可以为所有浏览器提供身份验证。您需要为您的服务器创建并提供一个公钥和一个私钥。这些密钥必须安全存储,不应更改。

您可以在实例化WebPush时指定您的身份验证详情。密钥可以直接传递(推荐),或者您可以加载一个PEM文件或其内容

<?php

use Minishlink\WebPush\WebPush;

$endpoint = 'https://fcm.googleapis.com/fcm/send/abcdef...'; // Chrome

$auth = [
    'VAPID' => [
        'subject' => 'mailto:me@website.com', // can be a mailto: or your website address
        'publicKey' => '~88 chars', // (recommended) uncompressed public key P-256 encoded in Base64-URL
        'privateKey' => '~44 chars', // (recommended) in fact the secret multiplier of the private key encoded in Base64-URL
        'pemFile' => 'path/to/pem', // if you have a PEM file and can link to it on your filesystem
        'pem' => 'pemFileContent', // if you have a PEM file and want to hardcode its content
    ],
];

$webPush = new WebPush($auth);
$webPush->queueNotification(...);

要生成未压缩的公钥和私钥,以Base64编码,请在您的Linux bash中输入以下内容:

$ openssl ecparam -genkey -name prime256v1 -out private_key.pem
$ openssl ec -in private_key.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> public_key.txt
$ openssl ec -in private_key.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> private_key.txt

如果您无法访问Linux bash,可以打印createVapidKeys函数的输出

var_dump(VAPID::createVapidKeys()); // store the keys afterwards

在客户端,不要忘记使用VAPID公钥订阅为applicationServerKey:(urlBase64ToUint8Array来源 这里)

serviceWorkerRegistration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
})

重用VAPID标题

VAPID头部使用JSON Web Token (JWT)来验证您的身份。该令牌的有效负载包括订阅中包含的端点的协议和主机名以及一个过期时间戳(通常为12-24小时),并且使用您的公钥和私钥进行签名。因此,发送给同一推送服务的两个通知将使用相同的令牌,您可以在同一个刷新会话中重复使用它们,以提高性能。

$webPush->setReuseVAPIDHeaders(true);

通知和默认选项

每个通知都可以具有特定的存活时间、紧急程度和主题。WebPush标准规定紧急程度是可选的,但一些用户报告说,当未指定时,Safari会抛出错误。这可能在将来得到修复。您可以使用setDefaultOptions()或构造函数更改默认选项。

<?php

use Minishlink\WebPush\WebPush;

$defaultOptions = [
    'TTL' => 300, // defaults to 4 weeks
    'urgency' => 'normal', // protocol defaults to "normal". (very-low, low, normal, or high)
    'topic' => 'newEvent', // not defined by default. Max. 32 characters from the URL or filename-safe Base64 characters sets
    'batchSize' => 200, // defaults to 1000
];

// for every notifications
$webPush = new WebPush([], $defaultOptions);
$webPush->setDefaultOptions($defaultOptions);

// or for one notification
$webPush->sendOneNotification($subscription, $payload, ['TTL' => 5000]);

TTL

存活时间(TTL,以秒为单位)是推送消息在用户浏览器尚未可访问的情况下(例如,未连接)由推送服务(例如Mozilla)保留的时间。对于重要的通知,您可能希望使用非常长的时间。默认TTL为4周。然而,如果您发送多个非必要通知,则设置TTL为0:推送通知只有在用户当前连接时才会被发送。对于其他情况,如果您用户有多个时区,则应使用至少一天的TTL,如果他们没有,几小时就足够了。

紧急程度

紧急程度可以是“非常低”、“低”、“正常”或“高”。如果浏览器厂商实现了此功能,它将在移动设备上节省电池寿命(参看协议)。

主题

类似于旧版GCM服务器上的collapse_key,此字符串将使供应商只向用户显示此主题的最后一个通知(参看协议)。

batchSize

如果您一次性发送成千上万的通知,可能会因为Guzzle中端点的调用方式而导致内存溢出。为了解决这个问题,WebPush会分批发送通知。默认大小为1000。根据您的服务器配置(内存),您可能想减少这个数字。在实例化WebPush或调用setDefaultOptions时执行此操作。或者,如果您想为特定的刷新自定义此值,请将其作为参数传递:$webPush->flush($batchSize)

服务器错误

您可以看到浏览器厂商的服务器在遇到错误时返回的内容(例如,推送订阅过期、参数错误等)。

<?php

/** @var \Minishlink\WebPush\MessageSentReport $report */
foreach ($webPush->flush() as $report) {
    $endpoint = $report->getEndpoint();

    if ($report->isSuccess()) {
        echo "[v] Message sent successfully for subscription {$endpoint}.";
    } else {
        echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
        
        // also available (to get more info)
        
        /** @var \Psr\Http\Message\RequestInterface $requestToPushService */
        $requestToPushService = $report->getRequest();
        
        /** @var \Psr\Http\Message\ResponseInterface $responseOfPushService */
        $responseOfPushService = $report->getResponse();
        
        /** @var string $failReason */
        $failReason = $report->getReason();
        
        /** @var bool $isTheEndpointWrongOrExpired */
        $isTheEndpointWrongOrExpired = $report->isSubscriptionExpired();
    }
}

请注意:您只能遍历一次\Generator对象。

Firefox错误在autopush文档中列出。

有效负载长度、安全和性能

库会加密有效负载。理论上的最大有效负载长度为4078字节(或ASCII字符)。不过,由于兼容性原因,您的有效负载长度应小于3052字节。

库默认会填充有效负载。这更安全,但会降低您服务器和用户设备上的性能。

为什么更安全?

当你加密一个特定长度的字符串时,无论你加密原始字符串多少次,结果字符串的长度始终保持不变。这可能会让攻击者猜测有效载荷的内容。为了解决这个问题,这个库在原始有效载荷中添加了一些空填充,以确保加密过程中的所有输入都具有相同的长度。这样,加密过程中的所有输出也将具有相同的长度,攻击者将无法猜测你的有效载荷内容。

为什么它会降低性能?

加密更多字节需要服务器更多的运行时间,并且还会减慢用户的设备在解密时的速度。此外,发送和接收数据包也会花费更多时间。对于数据计划有限的用户来说,这也不是很友好。

如何禁用或自定义自动填充?

你可以根据需求自定义自动填充。

以下是一些设置建议

  • (默认) Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH (3052字节) 以与Android Firefox兼容
  • Encryption::MAX_PAYLOAD_LENGTH (4078字节) 以实现最大安全性
  • false 以实现最大性能
  • 如果你知道你的有效载荷不会超过 X 字节,那么将其设置为 X 以在安全性和性能之间取得最佳平衡。
<?php

use Minishlink\WebPush\WebPush;

$webPush = new WebPush();
$webPush->setAutomaticPadding(false); // disable automatic padding
$webPush->setAutomaticPadding(512); // enable automatic padding to 512 bytes (you should make sure that your payload is less than 512 bytes, or else an attacker could guess the content)
$webPush->setAutomaticPadding(true); // enable automatic padding to default maximum compatibility length

自定义HTTP客户端

WebPush使用Guzzle。它将使用它找到的最合适的客户端,大多数情况下将是MultiCurl,这允许并行发送多个通知。

你可以在实例化WebPush时自定义默认请求选项和超时。

<?php

use Minishlink\WebPush\WebPush;

$timeout = 20; // seconds
$clientOptions = [
    \GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => false,
]; // see \GuzzleHttp\RequestOptions
$webPush = new WebPush([], [], $timeout, $clientOptions);

常见问题

我的PHP框架有插件/包/扩展吗?

以下是一些可用选项

欢迎添加您自己的扩展!

关于安全性?

有效载荷根据Web Push消息加密标准加密,使用您可以通过遵循Web Push API规范获取的用户公钥和认证密钥。

内部,WebPush使用WebToken框架或OpenSSL来处理加密密钥生成和加密。

如何扩展?

以下是一些建议

  1. 确保你的服务器上安装了MultiCurl
  2. 在安全性和性能之间找到适合您需求的平衡(见上文)
  3. 找到合适的批量大小(在defaultOptions中设置或在flush()参数中设置)

如何解决“SSL证书问题:无法获取本地发行者证书”?

您的安装缺少一些证书。

  1. 下载 cacert.pem
  2. 编辑您的php.ini:在[curl]后,输入curl.cainfo = /path/to/cacert.pem

您还可以强制使用不进行对等验证的客户端。

如何解决“找不到类'Minishlink\WebPush\WebPush'”?

请确保使用Composer的自动加载器

require __DIR__ . '/path/to/vendor/autoload.php';

我丢失了我的VAPID密钥!

请参阅问题#58

我正在使用Firebase推送通知,我如何使用这个库?

这个库不是为Firebase推送通知设计的。您仍然可以将其用于您的Web项目(用于标准WebPush通知),但在使用库时,应该忘记与Firebase相关的任何链接。

我需要向原生应用程序发送通知。(例如,iOS的APNS)

WebPush用于Web应用程序。您需要像RMSPushNotificationsBundle(Symfony)这样的东西。

这是PHP...我需要JavaScript!

这个库受到了Node.js marco-c/web-push库的启发。

贡献

请参阅CONTRIBUTING.md

许可证

MIT