Giao thức đẩy web

Chúng ta đã thấy cách sử dụng thư viện để kích hoạt thông báo đẩy, nhưng chính xác thì các thư viện này đang làm gì?

Vâng, các lớp này đang tạo các yêu cầu mạng trong khi đảm bảo rằng các yêu cầu đó có định dạng phù hợp. Thông số kỹ thuật xác định yêu cầu mạng này là Giao thức đẩy web.

Sơ đồ về việc gửi thông báo đẩy từ máy chủ đến dịch vụ đẩy

Phần này trình bày cách máy chủ có thể tự xác định bằng khoá máy chủ ứng dụng và cách gửi tải trọng đã mã hoá cũng như dữ liệu liên quan.

Đây không phải là khía cạnh đẹp đẽ của tính năng đẩy web và tôi không phải là chuyên gia về mã hoá, nhưng hãy xem xét từng phần vì sẽ rất hữu ích khi biết những thư viện này đang làm gì.

Khoá máy chủ ứng dụng

Khi đăng ký người dùng, chúng ta sẽ truyền vào một applicationServerKey. Khoá này được truyền đến dịch vụ đẩy và dùng để kiểm tra xem ứng dụng đã đăng ký người dùng có phải là ứng dụng đang kích hoạt thông báo đẩy hay không.

Khi kích hoạt một thông báo đẩy, chúng ta sẽ gửi một tập hợp tiêu đề cho phép dịch vụ đẩy xác thực ứng dụng. (Điều này được xác định bằng thông số kỹ thuật VAPID.)

Tất cả những điều này có ý nghĩa gì và điều gì thực sự xảy ra? Dưới đây là các bước thực hiện để xác thực máy chủ ứng dụng:

  1. Máy chủ ứng dụng ký một số thông tin JSON bằng khoá ứng dụng riêng tư.
  2. Thông tin đã ký này được gửi đến dịch vụ đẩy dưới dạng tiêu đề trong yêu cầu POST.
  3. Dịch vụ đẩy sử dụng khoá công khai đã lưu trữ mà dịch vụ nhận được từ pushManager.subscribe() để kiểm tra xem thông tin nhận được có được ký bằng khoá riêng tư liên quan đến khoá công khai hay không. Lưu ý: Khoá công khai là applicationServerKey được truyền vào lệnh gọi đăng ký.
  4. Nếu thông tin đã ký là hợp lệ, dịch vụ đẩy sẽ gửi thông báo đẩy đến người dùng.

Dưới đây là ví dụ về luồng thông tin này. (Lưu ý chú giải ở dưới cùng bên trái để chỉ định khoá công khai và khoá riêng tư.)

Hình minh hoạ cách sử dụng khoá máy chủ ứng dụng riêng tư khi gửi thông báo

"Thông tin đã ký" được thêm vào tiêu đề trong yêu cầu là một Mã thông báo web JSON.

Mã thông báo web JSON

Mã thông báo web JSON (viết tắt là JWT) là một cách gửi thông báo đến bên thứ ba để người nhận có thể xác thực người gửi thông báo.

Khi nhận được thông báo, bên thứ ba cần lấy khoá công khai của người gửi và sử dụng khoá đó để xác thực chữ ký của JWT. Nếu chữ ký hợp lệ, thì JWT phải được ký bằng khoá riêng khớp với chữ ký đó và phải là của người gửi dự kiến.

Có một loạt thư viện trên https://jwt.io/ có thể thực hiện việc ký cho bạn và bạn nên thực hiện việc đó khi có thể. Để hoàn chỉnh, hãy xem cách tạo JWT đã ký theo cách thủ công.

Thông báo đẩy web và JWT đã ký

JWT đã ký chỉ là một chuỗi, mặc dù bạn có thể coi đó là ba chuỗi được nối với nhau bằng dấu chấm.

Hình minh hoạ các chuỗi trong Mã thông báo web JSON

Chuỗi đầu tiên và thứ hai (Thông tin JWT và dữ liệu JWT) là các phần của JSON đã được mã hoá base64, nghĩa là chuỗi này có thể đọc công khai.

Chuỗi đầu tiên là thông tin về chính JWT, cho biết thuật toán nào được dùng để tạo chữ ký.

Thông tin JWT cho thông báo đẩy web phải chứa các thông tin sau:

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

Chuỗi thứ hai là Dữ liệu JWT. Phần này cung cấp thông tin về người gửi JWT, người nhận và thời hạn hiệu lực của JWT.

Đối với thông báo đẩy web, dữ liệu sẽ có định dạng như sau:

{
  "aud": "https://some-push-service.org",
  "exp": "1469618703",
  "sub": "mailto:example@web-push-book.org"
}

Giá trị aud là "đối tượng", tức là đối tượng sử dụng JWT. Đối với thông báo đẩy web, đối tượng là dịch vụ đẩy, vì vậy, chúng ta đặt đối tượng này thành nguồn gốc của dịch vụ đẩy.

Giá trị exp là thời điểm hết hạn của JWT, điều này ngăn chặn những kẻ rình mò sử dụng lại JWT nếu họ chặn JWT đó. Thời gian hết hạn là dấu thời gian tính bằng giây và không được dài hơn 24 giờ.

Trong Node.js, bạn có thể đặt thời gian hết hạn bằng cách sử dụng:

Math.floor(Date.now() / 1000) + 12 * 60 * 60;

Thời gian này là 12 giờ thay vì 24 giờ để tránh mọi vấn đề về sự khác biệt về đồng hồ giữa ứng dụng gửi và dịch vụ đẩy.

Cuối cùng, giá trị sub cần phải là một URL hoặc địa chỉ email mailto. Điều này là để nếu cần liên hệ với người gửi, dịch vụ đẩy có thể tìm thấy thông tin liên hệ từ JWT. (Đây là lý do thư viện web-push cần có địa chỉ email).

Giống như Thông tin JWT, Dữ liệu JWT được mã hoá dưới dạng chuỗi base64 an toàn cho URL.

Chuỗi thứ ba, chữ ký, là kết quả của việc lấy hai chuỗi đầu tiên (Thông tin JWT và Dữ liệu JWT), nối chúng với một ký tự dấu chấm, chúng ta sẽ gọi là "mã thông báo chưa ký" và ký mã thông báo đó.

Quy trình ký yêu cầu mã hoá "mã thông báo chưa ký" bằng ES256. Theo quy cách JWT, ES256 là viết tắt của "ECDSA sử dụng hàm số P-256 và thuật toán băm SHA-256". Khi sử dụng mã hoá web, bạn có thể tạo chữ ký như sau:

// Utility function for UTF-8 encoding a string to an ArrayBuffer.
const utf8Encoder = new TextEncoder('utf-8');

// The unsigned token is the concatenation of the URL-safe base64 encoded
// header and body.
const unsignedToken = .....;

// Sign the |unsignedToken| using ES256 (SHA-256 over ECDSA).
const key = {
  kty: 'EC',
  crv: 'P-256',
  x: window.uint8ArrayToBase64Url(
    applicationServerKeys.publicKey.subarray(1, 33)),
  y: window.uint8ArrayToBase64Url(
    applicationServerKeys.publicKey.subarray(33, 65)),
  d: window.uint8ArrayToBase64Url(applicationServerKeys.privateKey),
};

// Sign the |unsignedToken| with the server's private key to generate
// the signature.
return crypto.subtle.importKey('jwk', key, {
  name: 'ECDSA', namedCurve: 'P-256',
}, true, ['sign'])
.then((key) => {
  return crypto.subtle.sign({
    name: 'ECDSA',
    hash: {
      name: 'SHA-256',
    },
  }, key, utf8Encoder.encode(unsignedToken));
})
.then((signature) => {
  console.log('Signature: ', signature);
});

Dịch vụ đẩy có thể xác thực JWT bằng cách sử dụng khoá máy chủ ứng dụng công khai để giải mã chữ ký và đảm bảo chuỗi đã giải mã giống với "mã thông báo chưa ký" (tức là hai chuỗi đầu tiên trong JWT).

JWT đã ký (tức là cả ba chuỗi được nối với nhau bằng dấu chấm) được gửi đến dịch vụ đẩy web dưới dạng tiêu đề AuthorizationWebPush ở đầu, như sau:

Authorization: 'WebPush [JWT Info].[JWT Data].[Signature]';

Giao thức đẩy web cũng nêu rõ rằng khoá máy chủ ứng dụng công khai phải được gửi trong tiêu đề Crypto-Key dưới dạng chuỗi được mã hoá base64 an toàn cho URL, trong đó có thêm p256ecdsa= vào đầu.

Crypto-Key: p256ecdsa=[URL Safe Base64 Public Application Server Key]

Mã hoá tải trọng

Tiếp theo, hãy xem cách chúng ta có thể gửi tải trọng bằng thông báo đẩy để khi ứng dụng web nhận được thông báo đẩy, ứng dụng đó có thể truy cập vào dữ liệu nhận được.

Một câu hỏi thường gặp của những người đã sử dụng các dịch vụ đẩy khác là tại sao tải trọng đẩy web cần được mã hoá? Với ứng dụng gốc, thông báo đẩy có thể gửi dữ liệu dưới dạng văn bản thuần tuý.

Một phần của sự hấp dẫn của tính năng đẩy web là vì tất cả các dịch vụ đẩy đều sử dụng cùng một API (giao thức đẩy web), nên nhà phát triển không cần quan tâm đến dịch vụ đẩy đó là gì. Chúng ta có thể tạo một yêu cầu ở định dạng phù hợp và mong đợi một thông báo đẩy được gửi. Nhược điểm của việc này là nhà phát triển có thể gửi thông báo đến một dịch vụ đẩy không đáng tin cậy. Bằng cách mã hoá tải trọng, dịch vụ đẩy không thể đọc dữ liệu được gửi. Chỉ trình duyệt mới có thể giải mã thông tin đó. Điều này giúp bảo vệ dữ liệu của người dùng.

Quá trình mã hoá tải trọng được xác định trong thông số kỹ thuật về Mã hoá thông báo.

Trước khi xem các bước cụ thể để mã hoá tải trọng của thông báo đẩy, chúng ta nên tìm hiểu một số kỹ thuật sẽ được sử dụng trong quá trình mã hoá. (Xin cảm ơn Mat Scales rất nhiều vì bài viết tuyệt vời của anh về việc mã hoá đẩy.)

ECDH và HKDF

Cả ECDH và HKDF đều được sử dụng trong suốt quá trình mã hoá và mang lại lợi ích cho mục đích mã hoá thông tin.

ECDH: Trao đổi khoá Diffie-Hellman trên đường cong elip

Hãy tưởng tượng bạn có hai người muốn chia sẻ thông tin, Alice và Bob. Cả Alice và Bob đều có khoá công khai và khoá riêng tư riêng. Alice và Bob chia sẻ khoá công khai của họ với nhau.

Thuộc tính hữu ích của các khoá được tạo bằng ECDH là Alice có thể sử dụng khoá riêng tư của mình và khoá công khai của Bob để tạo giá trị bí mật "X". Bob cũng có thể làm tương tự, lấy khoá riêng tư của mình và khoá công khai của Alice để tạo độc lập cùng một giá trị "X". Điều này khiến "X" trở thành một bí mật dùng chung và Alice và Bob chỉ cần chia sẻ khoá công khai của họ. Giờ đây, Bob và Alice có thể sử dụng "X" để mã hoá và giải mã tin nhắn giữa họ.

Theo như tôi biết, ECDH xác định các thuộc tính của các đường cong cho phép "tính năng" này tạo một khoá bí mật dùng chung "X".

Đây là nội dung giải thích tổng quan về ECDH. Nếu muốn tìm hiểu thêm, bạn nên xem video này.

Về mã; hầu hết các ngôn ngữ / nền tảng đều có thư viện để dễ dàng tạo các khoá này.

Trong nút, chúng ta sẽ làm như sau:

const keyCurve = crypto.createECDH('prime256v1');
keyCurve.generateKeys();

const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();

HKDF: Hàm dẫn xuất khoá dựa trên HMAC

Wikipedia có nội dung mô tả ngắn gọn về HKDF:

HKDF là một hàm dẫn xuất khoá dựa trên HMAC, giúp chuyển đổi mọi nội dung khoá yếu thành nội dung khoá mạnh về mặt mã hoá. Ví dụ: bạn có thể dùng hàm này để chuyển đổi bí mật được chia sẻ đã trao đổi Diffie Hellman thành tài liệu khoá phù hợp để sử dụng trong quá trình mã hoá, kiểm tra tính toàn vẹn hoặc xác thực.

Về cơ bản, HKDF sẽ lấy dữ liệu đầu vào không an toàn và tăng cường bảo mật cho dữ liệu đó.

Thông số kỹ thuật xác định phương thức mã hoá này yêu cầu sử dụng SHA-256 làm thuật toán băm và các khoá thu được cho HKDF trong tính năng đẩy web không được dài hơn 256 bit (32 byte).

Trong nút, bạn có thể triển khai như sau:

// Simplified HKDF, returning keys up to 32 bytes long
function hkdf(salt, ikm, info, length) {
  // Extract
  const keyHmac = crypto.createHmac('sha256', salt);
  keyHmac.update(ikm);
  const key = keyHmac.digest();

  // Expand
  const infoHmac = crypto.createHmac('sha256', key);
  infoHmac.update(info);

  // A one byte long buffer containing only 0x01
  const ONE_BUFFER = new Buffer(1).fill(1);
  infoHmac.update(ONE_BUFFER);

  return infoHmac.digest().slice(0, length);
}

Cảm ơn bài viết của Mat Scale về mã mẫu này.

Phần này đề cập một cách lỏng lẻo đến ECDHHKDF.

ECDH là một cách an toàn để chia sẻ khoá công khai và tạo khoá bí mật dùng chung. HKDF là một cách để lấy tài liệu không an toàn và bảo mật tài liệu đó.

Giá trị này sẽ được dùng trong quá trình mã hoá tải trọng. Tiếp theo, hãy xem xét những gì chúng ta coi là dữ liệu đầu vào và cách mã hoá dữ liệu đó.

Thông tin đầu vào

Khi muốn gửi thông báo đẩy đến người dùng có tải trọng, chúng ta cần có 3 thông tin đầu vào:

  1. Chính tải trọng.
  2. Bí mật auth từ PushSubscription.
  3. Khoá p256dh từ PushSubscription.

Chúng ta đã thấy các giá trị authp256dh được truy xuất từ PushSubscription, nhưng để nhắc lại nhanh, với một gói thuê bao, chúng ta cần các giá trị sau:

subscription.toJSON().keys.auth;
subscription.toJSON().keys.p256dh;

subscription.getKey('auth');
subscription.getKey('p256dh');

Bạn phải coi giá trị auth là bí mật và không được chia sẻ ra bên ngoài ứng dụng.

Khoá p256dh là khoá công khai, đôi khi được gọi là khoá công khai của ứng dụng. Ở đây, chúng ta sẽ gọi p256dh là khoá công khai của gói thuê bao. Khoá công khai của gói thuê bao do trình duyệt tạo. Trình duyệt sẽ giữ bí mật khoá riêng tư và sử dụng khoá này để giải mã tải trọng.

Ba giá trị này (auth, p256dhpayload) là cần thiết để làm dữ liệu đầu vào và kết quả của quá trình mã hoá sẽ là tải trọng đã mã hoá, giá trị dữ liệu ngẫu nhiên và khoá công khai chỉ dùng để mã hoá dữ liệu.

Muối

Dữ liệu ngẫu nhiên này phải là 16 byte. Trong NodeJS, chúng ta sẽ làm như sau để tạo một giá trị muối:

const salt = crypto.randomBytes(16);

Khoá công khai / khoá riêng tư

Bạn nên tạo khoá công khai và khoá riêng tư bằng cách sử dụng đường cong elip P-256. Chúng ta sẽ thực hiện việc này trong Node như sau:

const localKeysCurve = crypto.createECDH('prime256v1');
localKeysCurve.generateKeys();

const localPublicKey = localKeysCurve.getPublicKey();
const localPrivateKey = localKeysCurve.getPrivateKey();

Chúng tôi sẽ gọi các khoá này là "khoá cục bộ". Các khoá này chỉ được dùng để mã hoá và không liên quan đến khoá máy chủ ứng dụng.

Với tải trọng, khoá xác thực bí mật và khoá công khai của gói thuê bao làm dữ liệu đầu vào, cùng với một giá trị muối mới tạo và tập hợp khoá cục bộ, chúng ta đã sẵn sàng thực hiện một số hoạt động mã hoá.

Khoá bí mật dùng chung

Bước đầu tiên là tạo một khoá bí mật dùng chung bằng khoá công khai của gói thuê bao và khoá riêng mới của chúng ta (hãy nhớ nội dung giải thích về ECDH với Alice và Bob? Chỉ đơn giản vậy thôi).

const sharedSecret = localKeysCurve.computeSecret(
  subscription.keys.p256dh,
  'base64',
);

Giá trị này được dùng trong bước tiếp theo để tính Khoá ngẫu nhiên giả (PRK).

Khoá giả ngẫu nhiên

Khoá ngẫu nhiên giả lập (PRK) là tổ hợp của khoá xác thực của gói thuê bao đẩy và khoá bí mật dùng chung mà chúng ta vừa tạo.

const authEncBuff = new Buffer('Content-Encoding: auth\0', 'utf8');
const prk = hkdf(subscription.keys.auth, sharedSecret, authEncBuff, 32);

Bạn có thể thắc mắc chuỗi Content-Encoding: auth\0 dùng để làm gì. Tóm lại, thuộc tính này không có mục đích rõ ràng, mặc dù trình duyệt có thể giải mã một thông báo đến và tìm kiếm nội dung mã hoá dự kiến. \0 thêm một byte có giá trị là 0 vào cuối vùng đệm. Đây là điều mà trình duyệt dự kiến khi giải mã thông báo, trình duyệt sẽ dự kiến có nhiều byte để mã hoá nội dung, theo sau là một byte có giá trị 0, theo sau là dữ liệu đã mã hoá.

Khoá ngẫu nhiên giả lập của chúng ta chỉ đơn giản là chạy thông tin xác thực, thông tin bí mật dùng chung và một phần thông tin mã hoá thông qua HKDF (tức là tăng cường khả năng mã hoá).

Ngữ cảnh

"Ngữ cảnh" là một tập hợp các byte được dùng để tính toán hai giá trị sau này trong trình duyệt mã hoá. Về cơ bản, đây là một mảng byte chứa khoá công khai của gói thuê bao và khoá công khai cục bộ.

const keyLabel = new Buffer('P-256\0', 'utf8');

// Convert subscription public key into a buffer.
const subscriptionPubKey = new Buffer(subscription.keys.p256dh, 'base64');

const subscriptionPubKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = subscriptionPubKey.length;

const localPublicKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = localPublicKey.length;

const contextBuffer = Buffer.concat([
  keyLabel,
  subscriptionPubKeyLength.buffer,
  subscriptionPubKey,
  localPublicKeyLength.buffer,
  localPublicKey,
]);

Vùng đệm ngữ cảnh cuối cùng là một nhãn, số byte trong khoá công khai của gói thuê bao, theo sau là chính khoá đó, sau đó là số byte của khoá công khai cục bộ, theo sau là chính khoá đó.

Với giá trị ngữ cảnh này, chúng ta có thể sử dụng giá trị đó để tạo một số chỉ dùng một lần và khoá mã hoá nội dung (CEK).

Khoá mã hoá nội dung và số chỉ dùng một lần

Số chỉ dùng một lần là một giá trị ngăn chặn các cuộc tấn công phát lại vì giá trị này chỉ được dùng một lần.

Khoá mã hoá nội dung (CEK) là khoá cuối cùng sẽ được dùng để mã hoá tải trọng của chúng ta.

Trước tiên, chúng ta cần tạo các byte dữ liệu cho số chỉ dùng một lần và CEK. Đây chỉ là một chuỗi mã hoá nội dung, theo sau là bộ đệm ngữ cảnh mà chúng ta vừa tính toán:

const nonceEncBuffer = new Buffer('Content-Encoding: nonce\0', 'utf8');
const nonceInfo = Buffer.concat([nonceEncBuffer, contextBuffer]);

const cekEncBuffer = new Buffer('Content-Encoding: aesgcm\0');
const cekInfo = Buffer.concat([cekEncBuffer, contextBuffer]);

Thông tin này được chạy thông qua HKDF kết hợp salt và PRK với nonceInfo và cekInfo:

// The nonce should be 12 bytes long
const nonce = hkdf(salt, prk, nonceInfo, 12);

// The CEK should be 16 bytes long
const contentEncryptionKey = hkdf(salt, prk, cekInfo, 16);

Thao tác này sẽ cung cấp cho chúng ta số chỉ dùng một lần và khoá mã hoá nội dung.

Thực hiện mã hoá

Giờ đây, khi đã có khoá mã hoá nội dung, chúng ta có thể mã hoá tải trọng.

Chúng ta tạo một thuật toán mã hoá AES128 bằng cách sử dụng khoá mã hoá nội dung làm khoá và số chỉ dùng một lần là vectơ khởi tạo.

Trong Node, bạn có thể thực hiện như sau:

const cipher = crypto.createCipheriv(
  'id-aes128-GCM',
  contentEncryptionKey,
  nonce,
);

Trước khi mã hoá tải trọng, chúng ta cần xác định lượng khoảng đệm mà chúng ta muốn thêm vào đầu tải trọng. Lý do chúng ta muốn thêm khoảng đệm là để ngăn chặn nguy cơ kẻ nghe lén có thể xác định "loại" thông điệp dựa trên kích thước tải trọng.

Bạn phải thêm 2 byte khoảng đệm để cho biết độ dài của khoảng đệm bổ sung.

Ví dụ: nếu không thêm khoảng đệm, bạn sẽ có hai byte có giá trị 0, tức là không có khoảng đệm nào, sau hai byte này, bạn sẽ đọc được tải trọng. Nếu bạn thêm 5 byte khoảng đệm, thì hai byte đầu tiên sẽ có giá trị là 5, do đó, trình dùng sẽ đọc thêm 5 byte rồi bắt đầu đọc tải trọng.

const padding = new Buffer(2 + paddingLength);
// The buffer must be only zeros, except the length
padding.fill(0);
padding.writeUInt16BE(paddingLength, 0);

Sau đó, chúng ta chạy khoảng đệm và tải trọng thông qua thuật toán mã hoá này.

const result = cipher.update(Buffer.concat(padding, payload));
cipher.final();

// Append the auth tag to the result -
// https://nodejs.org/api/crypto.html#crypto_cipher_getauthtag
const encryptedPayload = Buffer.concat([result, cipher.getAuthTag()]);

Bây giờ, chúng ta đã có tải trọng được mã hoá. Thật tuyệt vời!

Việc còn lại là xác định cách tải trọng này được gửi đến dịch vụ đẩy.

Tiêu đề và nội dung tải trọng đã mã hoá

Để gửi tải trọng đã mã hoá này đến dịch vụ đẩy, chúng ta cần xác định một số tiêu đề khác nhau trong yêu cầu POST.

Tiêu đề mã hoá

Tiêu đề "Encryption" (Mã hoá) phải chứa salt dùng để mã hoá tải trọng.

Giá trị số chỉ dùng một lần 16 byte phải được mã hoá an toàn theo URL base64 và thêm vào tiêu đề Mã hoá, như sau:

Encryption: salt=[URL Safe Base64 Encoded Salt]

Tiêu đề khoá mã hoá

Chúng ta đã thấy tiêu đề Crypto-Key được sử dụng trong phần "Khoá máy chủ ứng dụng" để chứa khoá máy chủ ứng dụng công khai.

Tiêu đề này cũng được dùng để chia sẻ khoá công khai cục bộ dùng để mã hoá tải trọng.

Tiêu đề thu được sẽ có dạng như sau:

Crypto-Key: dh=[URL Safe Base64 Encoded Local Public Key String]; p256ecdsa=[URL Safe Base64 Encoded Public Application Server Key]

Tiêu đề loại nội dung, độ dài và mã hoá

Tiêu đề Content-Length là số byte trong tải trọng đã mã hoá. Tiêu đề "Content-Type" và "Content-Encoding" là các giá trị cố định. Điều này được thể hiện bên dưới.

Content-Length: [Number of Bytes in Encrypted Payload]
Content-Type: 'application/octet-stream'
Content-Encoding: 'aesgcm'

Khi đã đặt các tiêu đề này, chúng ta cần gửi tải trọng đã mã hoá dưới dạng nội dung của yêu cầu. Lưu ý rằng Content-Type được đặt thành application/octet-stream. Điều này là do tải trọng đã mã hoá phải được gửi dưới dạng luồng byte.

Trong NodeJS, chúng ta sẽ thực hiện như sau:

const pushRequest = https.request(httpsOptions, function(pushResponse) {
pushRequest.write(encryptedPayload);
pushRequest.end();

Bạn muốn thêm tiêu đề khác?

Chúng ta đã đề cập đến các tiêu đề dùng cho JWT / Khoá máy chủ ứng dụng (tức là cách xác định ứng dụng bằng dịch vụ đẩy) và chúng ta đã đề cập đến các tiêu đề dùng để gửi tải trọng đã mã hoá.

Có các tiêu đề bổ sung mà các dịch vụ đẩy sử dụng để thay đổi hành vi của các thông báo đã gửi. Một số tiêu đề trong số này là bắt buộc, còn lại là không bắt buộc.

Tiêu đề TTL

Bắt buộc

TTL (hoặc thời gian tồn tại) là một số nguyên chỉ định số giây mà bạn muốn tin nhắn đẩy tồn tại trên dịch vụ đẩy trước khi được phân phối. Khi TTL hết hạn, thông báo sẽ bị xoá khỏi hàng đợi dịch vụ đẩy và sẽ không được gửi.

TTL: [Time to live in seconds]

Nếu bạn đặt TTL bằng 0, dịch vụ đẩy sẽ cố gắng phân phối thông báo ngay lập tức, nhưng nếu không thể kết nối với thiết bị, thông báo của bạn sẽ ngay lập tức bị xoá khỏi hàng đợi dịch vụ đẩy.

Về mặt kỹ thuật, dịch vụ đẩy có thể giảm TTL của thông báo đẩy nếu muốn. Bạn có thể biết điều này đã xảy ra hay chưa bằng cách kiểm tra tiêu đề TTL trong phản hồi từ dịch vụ đẩy.

Chủ đề

Không bắt buộc

Chủ đề là các chuỗi có thể dùng để thay thế một tin nhắn đang chờ xử lý bằng một tin nhắn mới nếu các tin nhắn đó có tên chủ đề trùng khớp.

Điều này hữu ích trong trường hợp nhiều thông báo được gửi khi thiết bị không có kết nối mạng và bạn chỉ muốn người dùng thấy thông báo mới nhất khi thiết bị bật.

Khẩn cấp

Không bắt buộc

Mức độ khẩn cấp cho dịch vụ đẩy biết tầm quan trọng của một thông báo đối với người dùng. Dịch vụ đẩy có thể sử dụng thông tin này để giúp tiết kiệm thời lượng pin của thiết bị người dùng bằng cách chỉ đánh thức thiết bị khi pin yếu để nhận thông báo quan trọng.

Giá trị tiêu đề được xác định như sau. Giá trị mặc định là normal.

Urgency: [very-low | low | normal | high]

Tất cả cùng nhau

Nếu có thêm câu hỏi về cách hoạt động của tất cả các thư viện này, bạn luôn có thể xem cách các thư viện kích hoạt thông báo đẩy trên web-push-libs org.

Sau khi có tải trọng đã mã hoá và các tiêu đề ở trên, bạn chỉ cần tạo một yêu cầu POST đến endpoint trong PushSubscription.

Vậy chúng ta sẽ làm gì với phản hồi cho yêu cầu POST này?

Phản hồi từ dịch vụ đẩy

Sau khi gửi yêu cầu đến dịch vụ đẩy, bạn cần kiểm tra mã trạng thái của phản hồi để biết yêu cầu có thành công hay không.

Mã trạng thái Mô tả
201 Đã tạo. Yêu cầu gửi thông báo đẩy đã được nhận và chấp nhận.
429 Quá nhiều yêu cầu. Tức là máy chủ ứng dụng của bạn đã đạt đến giới hạn tốc độ với dịch vụ đẩy. Dịch vụ đẩy phải có tiêu đề "Retry-After" (Thử lại sau) để cho biết thời gian chờ trước khi có thể thực hiện yêu cầu khác.
400 Yêu cầu không hợp lệ. Thông báo này thường có nghĩa là một trong các tiêu đề của bạn không hợp lệ hoặc có định dạng không chính xác.
404 Không tìm thấy. Điều này cho biết gói thuê bao đã hết hạn và không thể sử dụng được. Trong trường hợp này, bạn nên xoá `PushSubscription` và chờ ứng dụng đăng ký lại người dùng.
410 Đã xoá. Gói thuê bao không còn hợp lệ và cần được xoá khỏi máy chủ ứng dụng. Bạn có thể tái hiện vấn đề này bằng cách gọi `unsubscribe()` trên một `PushSubscription`.
413 Kích thước tải trọng quá lớn. Kích thước tối thiểu của tải trọng mà dịch vụ đẩy phải hỗ trợ là 4096 byte (hoặc 4 kb).

Bạn cũng có thể đọc Tiêu chuẩn Web Push (RFC8030) để biết thêm thông tin về mã trạng thái HTTP.

Bước tiếp theo

Lớp học lập trình