Chrome 最初支持 Web Push API 时,就是依赖于 Firebase Cloud Messaging (FCM)(以前称为 Google Cloud Messaging (GCM))推送服务。这需要使用其专有 API。这样一来,Chrome 便可以在网络推送协议规范仍在编写时向开发者提供 Web Push API,并在稍后没有网络推送协议时提供身份验证(即消息发送者与其所声称的身份相符)。好消息:现在这两种情况都不再是这样。
FCM / GCM 和 Chrome 现在支持标准网络推送协议,而发件人身份验证可通过实现 VAPID 来实现,这意味着您的 Web 应用不再需要“gcm_sender_id”。
在本文中,我首先介绍如何将现有服务器代码转换为将 Web 推送协议与 FCM 搭配使用。接下来,我将介绍如何 在客户端和服务器代码中实现 VAPID
FCM 支持网络推送协议
我们先来了解一些背景信息。当您的 Web 应用注册推送订阅时,系统会向其提供推送服务的网址。您的服务器将使用此端点通过您的 Web 应用向用户发送数据。在 Chrome 中,如果您订阅的用户未使用 VAPID,您将获得一个 FCM 端点。(稍后我们会介绍 VAPID)。在 FCM 支持网络推送协议之前,您必须先从网址末尾提取 FCM 注册 ID,并将其放入标头中,然后才能发出 FCM API 请求。例如,FCM 端点 https://android.googleapis.com/gcm/send/ABCD1234
的注册 ID 为“ABCD1234”。
由于 FCM 支持网络推送协议,因此您可以保持端点不变,并将网址用作网络推送协议端点。(这使得它能够与 Firefox 以及未来的所有其他浏览器保持一致。)
在深入了解 VAPID 之前,我们需要确保服务器代码可正确处理 FCM 端点。以下示例展示了如何在 Node 中向推送服务发出请求。请注意,对于 FCM,我们将 API 密钥添加到了请求标头中。对于其他推送服务端点,不需要此信息。对于版本 52 之前的 Chrome、Opera Android 和三星浏览器,您仍需要在 Web 应用的 manifest.json 中添加“gcm_sender_id”。API 密钥和发送者 ID 用于检查发出请求的服务器是否确实可以向接收用户发送消息。
const headers = new Headers();
// 12-hour notification time to live.
headers.append('TTL', 12 * 60 * 60);
// Assuming no data is going to be sent
headers.append('Content-Length', 0);
// Assuming you're not using VAPID (read on), this
// proprietary header is needed
if(subscription.endpoint
.indexOf('https://android.googleapis.com/gcm/send/') === 0) {
headers.append('Authorization', 'GCM_API_KEY');
}
fetch(subscription.endpoint, {
method: 'POST',
headers: headers
})
.then(response => {
if (response.status !== 201) {
throw new Error('Unable to send push message');
}
});
请注意,这是对 FCM / GCM 的 API 的更改,因此您无需更新订阅,只需更改服务器代码以定义标头即可,如上所述。
推出用于服务器识别的 VAPID
VAPID 是“自愿应用服务器标识”的炫酷新简称。这一新规范实质上定义了应用服务器与推送服务之间的握手,并允许推送服务确认哪个网站在发送消息。借助 VAPID,您可以省去发送推送消息时特定于 FCM 的步骤。您不再需要 Firebase 项目、gcm_sender_id
或 Authorization
标头。
操作过程非常简单:
- 应用服务器创建一个公钥/私钥对。公钥会被提供给您的 Web 应用。
- 当用户选择接收推送时,将公钥添加到 subscribe() 调用的 options 对象中。
- 当应用服务器发送推送消息时,请包含已签名的 JSON Web 令牌以及公钥。
我们来详细看一下这些步骤。
创建公钥/私钥对
我非常擅长加密,以下是规范中有关 VAPID 公钥/私钥格式的相关部分:
应用服务器应生成并维护一个签名密钥对,该密钥对可与 P-256 曲线上的椭圆曲线数字签名 (ECDSA) 结合使用。
您可以在 web-push 节点库中查看如何执行此操作:
function generateVAPIDKeys() {
var curve = crypto.createECDH('prime256v1');
curve.generateKeys();
return {
publicKey: curve.getPublicKey(),
privateKey: curve.getPrivateKey(),
};
}
使用公钥订阅
如需为 Chrome 用户订阅使用 VAPID 公钥的推送功能,您需要使用 subscription() 方法的 applicationServerKey
参数将公钥作为 Uint8Array 传递。
const publicKey = new Uint8Array([0x4, 0x37, 0x77, 0xfe, …. ]);
serviceWorkerRegistration.pushManager.subscribe(
{
userVisibleOnly: true,
applicationServerKey: publicKey
}
);
通过检查生成的订阅对象中的端点,即可了解订阅是否已正常运行;如果源是 fcm.googleapis.com
,则说明它运行正常。
https://fcm.googleapis.com/fcm/send/ABCD1234
发送推送消息
如需使用 VAPID 发送消息,您需要发出一个普通的 Web 推送协议请求,其中包含两个额外的 HTTP 标头:一个 Authorization 标头和一个 Crypto-Key 标头。
授权标头
Authorization
标头是一个签名的 JSON 网络令牌 (JWT),其前面带有“WebPush”。
JWT 是一种与第二方共享 JSON 对象的方式,采用这种方式时,发送方可以对其进行签名,而接收方可以验证签名是否来自预期的发送方。JWT 的结构是三个加密字符串,它们之间用一个点连接。
<JWTHeader>.<Payload>.<Signature>
JWT 标头
JWT 标头包含用于签名的算法名称和令牌类型。对于 VAPID,必须满足以下条件:
{
"typ": "JWT",
"alg": "ES256"
}
然后,采用 base64 网址编码并形成 JWT 的第一部分。
载荷
载荷是另一个包含以下内容的 JSON 对象:
- 受众群体(“aud”)
- 这是推送服务的来源(不是您网站的来源)。
在 JavaScript 中,您可以执行以下操作来获取受众群体:
const audience = new URL(subscription.endpoint).origin
- 这是推送服务的来源(不是您网站的来源)。
在 JavaScript 中,您可以执行以下操作来获取受众群体:
- 到期时间(“exp”)
- 这是指请求被视为过期的秒数。此时间必须在发出请求后的 24 小时内(采用世界协调时间 (UTC))。
- 主题(“sub”)
- 主题必须是网址或
mailto:
网址。这可提供一个联系人,以防推送服务需要与消息发送者联系。
- 主题必须是网址或
示例载荷可能如下所示:
{
"aud": "http://push-service.example.com",
"exp": Math.floor((Date.now() / 1000) + (12 * 60 * 60)),
"sub": "mailto: my-email@some-url.com"
}
此 JSON 对象采用 base64 网址编码,构成 JWT 的第二部分。
签名
签名是将编码标头和载荷与点联接,然后使用您之前创建的 VAPID 私钥对结果进行加密。结果本身应以一个点附加到标头。
我不会展示相关的代码示例,因为有很多库将采用标头和载荷 JSON 对象并为您生成此签名。
已签名的 JWT 用作授权标头,并在其前面加上“WebPush”,如下所示:
WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A
请注意这方面的一些事项。首先,授权标头包含单词“WebPush”,后跟一个空格,接着是 JWT。另请注意用点分隔 JWT 标头、载荷和签名。
Crypto-Key 标头
除了 Authorization 标头之外,您还必须以 base64 网址编码字符串的形式将 VAPID 公钥添加到 Crypto-Key
标头中,并在其前面加上 p256ecdsa=
。
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo
当您发送包含加密数据的通知时,您已在使用 Crypto-Key
标头。因此,如需添加应用服务器密钥,只需在添加上述内容前添加一个英文分号,如下所示:
dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN
这些变化的实际情况
借助 VAPID,您无需再注册 GCM 的帐号即可在 Chrome 中使用推送功能。此外,您可以使用相同的代码路径在 Chrome 和 Firefox 中订阅用户及向用户发送消息。两者都符合相关标准。
需要注意的是,在 Chrome 51 及更低版本中,对于 Android 版 Opera 和三星浏览器,您仍然需要在 Web 应用清单中定义 gcm_sender_id
,并且需要将 Authorization 标头添加到要返回的 FCM 端点中。
VAPID 可以很好地满足这些专有要求。如果您实现 VAPID,它可在所有支持 Web 推送的浏览器中运行。随着越来越多的浏览器支持 VAPID,您可以决定何时从清单中删除 gcm_sender_id
。