웹 푸시 상호 운용성의 이점

Joe Medley
Joe Medley

Chrome에서 처음 웹 푸시 API를 지원할 때는 이전 명칭이 Google 클라우드 메시징 (GCM)인 Firebase 클라우드 메시징 (FCM) 푸시 서비스를 사용했습니다. 이를 위해서는 독점 API를 사용해야 했습니다. 이를 통해 Chrome은 웹 푸시 프로토콜 사양이 아직 작성 중일 때 개발자가 웹 푸시 API를 사용할 수 있도록 했으며 나중에 웹 푸시 프로토콜에 인증이 없을 때 인증 (메시지 발신자가 본인임을 증명함)을 제공했습니다. 좋은 소식은 더 이상 이러한 문제가 없다는 것입니다.

FCM / GCM, Chrome은 이제 표준 웹 푸시 프로토콜을 지원하지만 발신자 인증은 VAPID를 구현하여 실행할 수 있습니다. 즉, 웹 앱에 더 이상 'gcm_sender_id'가 필요하지 않습니다.

이 도움말에서는 먼저 FCM과 함께 웹 푸시 프로토콜을 사용하도록 기존 서버 코드를 변환하는 방법을 설명합니다. 다음으로 클라이언트 코드와 서버 코드 모두에서 VAPID를 구현하는 방법을 보여드리겠습니다.

FCM에서 웹 푸시 프로토콜을 지원합니다.

먼저 약간의 맥락을 살펴보겠습니다. 웹 애플리케이션이 푸시 구독을 등록하면 푸시 서비스의 URL이 제공됩니다. 서버는 이 엔드포인트를 사용하여 웹 앱을 통해 사용자에게 데이터를 전송합니다. Chrome에서는 VAPID 없이 사용자를 구독하면 FCM 엔드포인트가 제공됩니다. VAPID는 나중에 다룹니다. FCM에서 웹 푸시 프로토콜을 지원하기 전에는 FCM API 요청을 실행하기 전에 URL 끝에서 FCM 등록 ID를 추출하여 헤더에 넣어야 했습니다. 예를 들어 FCM 엔드포인트가 https://android.googleapis.com/gcm/send/ABCD1234인 경우 등록 ID는 'ABCD1234'입니다.

이제 FCM에서 웹 푸시 프로토콜을 지원하므로 엔드포인트를 그대로 두고 URL을 웹 푸시 프로토콜 엔드포인트로 사용할 수 있습니다. 이렇게 하면 Firefox 및 향후 모든 브라우저와 일치하게 됩니다.

VAPID를 자세히 살펴보기 전에 서버 코드가 FCM 엔드포인트를 올바르게 처리하는지 확인해야 합니다. 다음은 Node에서 푸시 서비스에 요청하는 예입니다. FCM의 경우 요청 헤더에 API 키가 추가됩니다. 다른 푸시 서비스 엔드포인트의 경우에는 필요하지 않습니다. 버전 52 이전의 Chrome, Opera Android, Samsung 브라우저의 경우에도 웹 앱의 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는 '자발적 애플리케이션 서버 ID'의 새로운 멋진 약어입니다. 이 새로운 사양은 기본적으로 앱 서버와 푸시 서비스 간의 핸드셰이크를 정의하고 푸시 서비스가 메시지를 전송하는 사이트를 확인할 수 있도록 합니다. VAPID를 사용하면 푸시 메시지를 전송하기 위한 FCM 관련 단계를 피할 수 있습니다. 더 이상 Firebase 프로젝트, gcm_sender_id 또는 Authorization 헤더가 필요하지 않습니다.

프로세스는 매우 간단합니다.

  1. 애플리케이션 서버가 공개 키/비공개 키 쌍을 생성합니다. 공개 키가 웹 앱에 제공됩니다.
  2. 사용자가 푸시를 수신하도록 선택하면 subscribe() 호출의 옵션 객체에 공개 키를 추가합니다.
  3. 앱 서버에서 푸시 메시지를 전송할 때 공개 키와 함께 서명된 JSON 웹 토큰을 포함합니다.

이러한 단계를 자세히 살펴보겠습니다.

공개 키/비공개 키 쌍 만들기

암호화는 잘 못하지만 VAPID 공개 키/비공개 키 형식과 관련된 사양의 관련 섹션은 다음과 같습니다.

애플리케이션 서버는 P-256 곡선에서 타원 곡선 디지털 서명 (ECDSA)으로 사용할 수 있는 서명 키 쌍을 생성하고 유지해야 합니다.

웹-푸시 노드 라이브러리에서 방법을 확인할 수 있습니다.

function generateVAPIDKeys() {
    var curve = crypto.createECDH('prime256v1');
    curve.generateKeys();

    return {
    publicKey: curve.getPublicKey(),
    privateKey: curve.getPrivateKey(),
    };
}

공개 키로 구독

VAPID 공개 키를 사용하여 Chrome 사용자를 푸시 구독하려면 subscribe() 메서드의 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 헤더(승인 헤더 및 Crypto-Key 헤더)로 일반 웹 푸시 프로토콜 요청을 해야 합니다.

승인 헤더

Authorization 헤더는 앞에 'WebPush '가 있는 서명된 JSON 웹 토큰 (JWT)입니다.

JWT는 JSON 객체를 두 번째 당사자와 공유하는 방법으로, 전송하는 당사자가 서명하고 수신하는 당사자는 예상한 발신자가 서명하였는지 확인할 수 있습니다. JWT의 구조는 암호화된 세 개의 문자열로, 문자열 사이에 단일 점으로 연결됩니다.

<JWTHeader>.<Payload>.<Signature>

JWT 헤더

JWT 헤더에는 서명에 사용된 알고리즘 이름과 토큰 유형이 포함됩니다. VAPID의 경우 다음과 같아야 합니다.

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

그런 다음 base64 URL로 인코딩되어 JWT의 첫 번째 부분이 형성됩니다.

페이로드

페이로드는 다음을 포함하는 또 다른 JSON 객체입니다.

  • 잠재고객 ('aud')
    • 사이트의 출처가 아닌 푸시 서비스의 출처입니다. JavaScript에서는 다음과 같이 잠재고객을 가져올 수 있습니다. const audience = new URL(subscription.endpoint).origin
  • 만료 시간('exp')
    • 요청이 만료된 것으로 간주될 때까지의 시간(초)입니다. UTC 기준으로 요청 후 24시간 이내에 반드시 완료해야 합니다.
  • 제목 ('sub')
    • 제목은 URL 또는 mailto: URL이어야 합니다. 이를 통해 푸시 서비스가 메시지 발신자에게 연락해야 하는 경우 연락할 수 있습니다.

페이로드의 예는 다음과 같습니다.

{
    "aud": "http://push-service.example.com",
    "exp": Math.floor((Date.now() / 1000) + (12 * 60 * 60)),
    "sub": "mailto: my-email@some-url.com"
}

이 JSON 객체는 base64 URL로 인코딩되며 JWT의 두 번째 부분을 구성합니다.

서명

서명은 인코딩된 헤더와 페이로드를 점으로 결합한 다음 앞에서 만든 VAPID 비공개 키를 사용하여 결과를 암호화한 결과입니다. 결과 자체는 점으로 헤더에 추가해야 합니다.

헤더와 페이로드 JSON 객체를 사용하여 이 서명을 생성하는 라이브러리가 여러 개 있으므로 이에 관한 코드 샘플은 표시하지 않겠습니다.

서명된 JWT는 'WebPush '가 접두사로 추가된 승인 헤더로 사용되며 다음과 같이 표시됩니다.

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

이 점에 관해 몇 가지 사항을 알아보겠습니다. 첫째, 승인 헤더에는 'WebPush'라는 단어가 포함되며 그 뒤에 공백과 JWT가 따라와야 합니다. 또한 JWT 헤더, 페이로드, 서명을 구분하는 점을 확인합니다.

Crypto-Key 헤더

승인 헤더 외에도 VAPID 공개 키를 Crypto-Key 헤더에 p256ecdsa=이 접두사로 추가된 base64 url 인코딩 문자열로 추가해야 합니다.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

암호화된 데이터가 포함된 알림을 전송하는 경우 이미 Crypto-Key 헤더를 사용하고 있으므로 애플리케이션 서버 키를 추가하려면 위의 콘텐츠를 추가하기 전에 세미콜론을 추가하기만 하면 됩니다. 그러면 다음과 같이 표시됩니다.

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

이번 변경사항의 실질적인 의미

VAPID를 사용하면 더 이상 Chrome에서 푸시를 사용하기 위해 GCM 계정에 가입할 필요가 없으며 Chrome과 Firefox에서 모두 사용자를 구독하고 사용자에게 메시지를 전송하는 데 동일한 코드 경로를 사용할 수 있습니다. 둘 다 표준을 따릅니다.

Chrome 51 이하, Android용 Opera, Samsung 브라우저에서는 웹 앱 매니페스트에 gcm_sender_id를 정의해야 하며 반환될 FCM 엔드포인트에 승인 헤더를 추가해야 합니다.

VAPID는 이러한 독점 요구사항에서 벗어날 수 있는 방법을 제공합니다. VAPID를 구현하면 웹 푸시를 지원하는 모든 브라우저에서 작동합니다. 점점 더 많은 브라우저에서 VAPID를 지원함에 따라 매니페스트에서 gcm_sender_id를 언제 삭제할지 결정할 수 있습니다.