網路推送互通性勝出

Matt Gaunt
喬梅利
Joe Medley

Chrome 首次支援 Web Push API 時,需要採用 Firebase 雲端通訊 (FCM),舊稱 Google 雲端通訊 (GCM) 推送服務。方法是使用專屬 API。如此一來,當 Chrome 仍在編寫網路推送通訊協定規格,且稍後提供驗證機制 (意即訊息傳送者的身分),則 Chrome 能在網路推送通訊協定缺少此規格時,向開發人員提供 Web Push API。好消息:以上皆非

FCM / GCM 和 Chrome 現在支援標準網路推送通訊協定,而傳送方可以實作 VAPID 來進行,代表網頁應用程式不再需要「gcm_sender_id」。

在本文中,會先說明如何轉換現有的伺服器程式碼,以便搭配 FCM 使用網路推送通訊協定。接下來,我將示範如何在用戶端和伺服器程式碼中實作 VAPID。

FCM 支援網路推送通訊協定

讓我們先概略瞭解背景資訊。當您的網頁應用程式註冊推送訂閱項目時,系統會提供推送服務的網址。您的伺服器會使用此端點,透過您的網頁應用程式將資料傳送給使用者。在 Chrome 中,如果您訂閱的使用者沒有 VAPID,則會獲得 FCM 端點。(稍後將介紹 VAPID)。在 FCM 支援網路推送通訊協定之前,您必須從網址結尾擷取 FCM 註冊 ID,並將其置於標頭中,才能提出 FCM API 要求。舉例來說,https://android.googleapis.com/gcm/send/ABCD1234 的 FCM 端點註冊 ID 為「ABCD1234」。

現在 FCM 支援網路推送通訊協定,您可以保留完整端點,並將該網址做為網路推播通訊協定端點。(這種方式與 Firefox 一致,希望未來所有瀏覽器都能支援)。

在深入探討 VAPID 之前,我們需要確認我們的伺服器程式碼能正確處理 FCM 端點。以下示範如何向節點中的推送服務提出要求。請注意,針對 FCM,我們會在要求標頭中加入 API 金鑰。如果是其他推送服務端點,則不需要這項權限。如果是 Opera Android 和 Samsung 瀏覽器 52 以下版本的 Chrome,您還需要在網頁應用程式的 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 是「Voluntary Application Server Identification」的新簡稱,這項新規格基本上定義了應用程式伺服器與推送服務之間的握手情形,並讓推送服務確認哪個網站正在傳送訊息。使用 VAPID 即可避免透過 FCM 專屬步驟傳送推送訊息。您不再需要 Firebase 專案、gcm_sender_idAuthorization 標頭。

這項程序相當簡單:

  1. 應用程式伺服器會建立公開/私密金鑰組。公開金鑰會提供給您的網頁應用程式。
  2. 使用者選擇接收推送時,請將公開金鑰新增至 subscription() 呼叫的選項物件。
  3. 應用程式伺服器傳送推送訊息時,請附上已簽署的 JSON Web Token 以及公開金鑰。

以下將詳細說明這些步驟。

建立公開/私密金鑰組

我沒辦法加密,因此以下有關 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 傳送訊息,您必須發出一般網路推送通訊協定要求,並加入兩個額外的 HTTP 標頭:授權標頭和加密編譯金鑰標頭。

Authorization 標頭

Authorization 標頭是已簽署的 JSON Web Token (JWT),前面有「WebPush」。

JWT 是與第二方共用 JSON 物件的一種方式,讓傳送方可以簽署該物件,且接收方可以驗證簽名是否來自預期的傳送者。JWT 的結構是三個加密字串,每個字串之間以一個點號聯結。

<JWTHeader>.<Payload>.<Signature>

JWT 標頭

JWT 標頭包含用於簽署的演算法名稱及權杖類型。如為 VAPID,則必須符合下列規定:

{
    "typ": "JWT",
    "alg": "ES256"
}

接著系統會使用 Base64 網址編碼,形成 JWT 的第一部分。

酬載

酬載是另一個包含下列項目的 JSON 物件:

  • 目標對象 (「目標對象」)
    • 這是指推送服務的來源 (「不是」網站的來源)。 在 JavaScript 中,您可以進行下列操作以觸及目標對象:const audience = new URL(subscription.endpoint).origin
  • 到期時間 (「exp」)
    • 這是直到要求到期為止的秒數。這個值必須在提出要求後的 24 小時內 (世界標準時間)。
  • 主旨 (「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 會做為 Authorization 標頭使用,並在後面加上「WebPush」,內容如下所示:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

請留意以下事項。首先,Authorization 標頭的意思是「WebPush」這個字,後面接著一個空格,然後是 JWT。另請注意,用於分隔 JWT 標頭、酬載和簽名的點。

加密編譯金鑰標頭

除了授權標頭,您也必須將 VAPID 公開金鑰新增至 Crypto-Key 標頭,做為 Base64 網址編碼字串,並在前面加上 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 和 Samsung 瀏覽器適用的 Opera 您仍然需要在網頁應用程式資訊清單中定義 gcm_sender_id,然後將 Authorization 標頭新增至系統傳回的 FCM 端點。

VAPID 可讓您根據這些專屬要求進行擴充。如果您實作 VAPID,則它在支援網路推送功能的所有瀏覽器中都能夠正常運作。由於更多瀏覽器支援 VAPID,因此您可以決定何時從資訊清單中捨棄 gcm_sender_id