Firebase Cloud Messaging
고친 사람 github-actions[bot]
Firebase Cloud Messaging 은 백엔드 서버가 보낸 알림을 사용자 기기의 앱까지 날라 주는 Google 의 서비스입니다. 서버는 받을 앱을 가리키는 값과 알림 내용을 이 서비스에 넘기기만 합니다. 휴대폰까지 가는 길은 이 서비스가 맡습니다. Android 앱이 알림을 받는 기본 통로입니다. iOS 앱과 웹 브라우저로도 보낼 수 있습니다.
쉽고 빠른 이해
무슨 일을 하나 — 우리 서버가 보낸 알림을 사용자 휴대폰의 앱까지 전해 줍니다. 쇼핑몰 서버가 「주문하신 상품이 출발했습니다」를 넘기면, 앱을 닫아 두었어도 휴대폰 화면에 그 알림이 뜹니다.
왜 필요한가 — 우리 서버는 사용자 휴대폰에 먼저 연결하지 못합니다. 휴대폰은 늘 Google 서버와 연결 하나를 열어 두고 있습니다. 그 연결을 가진 Google 이 대신 전해 줘야 알림이 닿습니다.
어떻게 도나
- 앱이 켜질 때 이 서비스에서 자기를 가리키는 주소 값을 받아 우리 서버에 맡깁니다
- 알릴 일이 생기면 우리 서버가 그 주소 값과 내용을 이 서비스에 보냅니다
- 이 서비스가 기기 종류에 맞는 길로 넘깁니다. Android 는 Google 의 연결로, iOS 는 Apple 의 알림 서버로 갑니다
대가 — 도착을 약속받지 못합니다. Google Play 서비스(Google 이 Android 휴대폰에 미리 깔아 두는 시스템 앱)가 빠진 휴대폰에는 이 길이 없습니다. 알림 한 통 한 통이 Google 서버를 거치므로 그 서비스에 기대게 됩니다. 앱이 화면에 떠 있을 때나 받는 쪽이 서버일 때는 이 길 말고 다른 길을 씁니다.
상세
Firebase Cloud Messaging(FCM)은 Firebase 가 내놓은 서비스 가운데 메시지 전달을 맡는 것입니다. Firebase 는 Google 이 운영하는 앱 개발용 백엔드 서비스 묶음입니다. 로그인, 데이터베이스, 오류 수집 같은 기능이 함께 들어 있습니다.
이 절은 FCM 이 백엔드 대신 무엇을 해 주는지부터 봅니다. 그다음 백엔드 개발자가 FCM 을 부를 때 손에 쥐는 것들을 차례로 봅니다. 등록 토큰, 보내는 요청과 인증, 메시지 두 종류, 토픽, 우선순위, 도착 보장 순서입니다.
백엔드가 기기에 직접 못 보내는 까닭
푸시 알림은 서버가 먼저 사용자 기기로 보내는 짧은 메시지입니다. 메신저의 새 메시지 알림, 배달 앱의 출발 알림이 그런 메시지입니다. 앱을 열어 두지 않아도 잠금 화면에 뜹니다.
그런데 백엔드 서버는 사용자의 휴대폰에 먼저 연결을 열지 못합니다.
휴대폰은 대개 통신사 망이나 공유기 뒤에 있습니다. 이런 장비는 안쪽 기기 여럿이 바깥 주소 하나를 나눠 쓰게 합니다. 이 일을 NAT(Network Address Translation, 네트워크 주소 변환)라고 부릅니다. NAT 뒤의 기기는 밖에서 먼저 여는 연결을 받지 않습니다.
그래서 휴대폰 쪽에서 먼저 연결을 엽니다. Android 휴대폰은 켜져 있는 동안 Google 서버와 연결 하나를 계속 열어 둡니다. 휴대폰이 연 연결이라 NAT 뒤에서도 살아 있습니다. FCM 은 이 연결을 타고 알림을 내려보냅니다.
모든 앱의 알림이 이 연결 하나를 나눠 씁니다. 앱마다 제 서버와 연결을 따로 붙잡으면 연결 수만큼 배터리가 닳기 때문입니다. 백엔드 개발자가 보기에 FCM 은 이 공용 연결로 들어가는 입구입니다.
등록 토큰
FCM 은 알림을 어느 기기의 어느 앱에 줄지 알아야 합니다. 그 주소 노릇을 하는 값이 등록 토큰(registration token)입니다. 「이 휴대폰에 깔린 이 앱」 하나를 가리키는 긴 문자열입니다. 같은 앱이라도 휴대폰이 다르면 토큰이 다릅니다.
앱은 이 토큰을 FCM 의 SDK(Software Development Kit, 소프트웨어 개발 키트)에서 받습니다. SDK 는 앱에 넣어 쓰는 라이브러리 묶음입니다. 앱은 받은 토큰을 우리 백엔드로 보냅니다. 백엔드는 그 토큰을 사용자 계정에 묶어 저장합니다.
토큰은 바뀝니다. 앱을 지웠다 다시 깔거나 앱 데이터를 지우면 새 토큰이 나옵니다. 새 토큰이 나오면 SDK 가 앱 코드에 알려 줍니다. 앱은 그때 새 토큰을 백엔드에 다시 보냅니다.
낡은 토큰으로 보내면 FCM 이 「등록되지 않은 토큰」이라는 오류(UNREGISTERED)를 돌려줍니다. 백엔드는 이 오류를 받으면 그 토큰을 지웁니다. 안 지우면 이미 없는 앱에 계속 보내느라 요청만 늘어납니다.
메시지가 기기까지 가는 길
백엔드에서 나간 메시지 하나는 기기 종류에 따라 다른 길로 갈라집니다. 백엔드는 받는 기기가 무엇이든 FCM 서버 한 곳에만 보냅니다. 갈라지는 일은 FCM 서버가 맡습니다.
flowchart TD
A["백엔드 서버"] --> B["FCM 서버"]
B --> C["Google Play 서비스 · Android"]
B --> D["Apple 알림 서버 · iOS"]
B --> E["브라우저의 푸시 서비스 · 웹"]
C --> F["휴대폰 안의 앱"]
D --> F
E --> G["브라우저의 서비스 워커"]
FCM 서버는 토큰을 보고 그 앱이 어느 플랫폼에 깔렸는지 압니다. 그리고 플랫폼마다 정해진 중계자에게 넘깁니다. 그림의 가운데 줄이 그 중계자 셋입니다. 맨 아래 줄은 메시지를 마지막으로 받는 쪽입니다.
Android 쪽 중계자는 Google Play 서비스입니다. Google 이 Android 휴대폰에 미리 깔아 두는 시스템 앱입니다. 앞에서 본 공용 연결을 쥐고 있는 것이 이 앱입니다.
iOS 쪽 중계자는 APNs(Apple Push Notification service)입니다. Apple 이 운영하는 알림 중계 서버입니다. iOS 기기에서는 Apple 만 기기와 연결을 열어 두므로 FCM 도 APNs 를 거칩니다.
웹 쪽 중계자는 브라우저를 만든 회사가 운영하는 푸시 서비스입니다. 브라우저로 보내는 이런 알림을 웹 푸시라고 부릅니다.
웹에서 메시지를 마지막으로 받는 쪽은 페이지가 아니라 서비스 워커입니다. 서비스 워커는 페이지와 따로 브라우저 뒤에서 도는 스크립트입니다. 페이지가 닫혀 있어도 브라우저가 이 스크립트를 깨워 메시지를 건넵니다. 세 갈래가 저마다 FCM 과 어떻게 붙는지는 아래 「맞물림」 절에 있습니다.
보내는 요청
백엔드 입장에서 알림 하나는 FCM 서버에 보내는 HTTP(HyperText Transfer Protocol) 요청 하나입니다.
요청은 FCM 의 HTTP v1 API(Application Programming Interface)로 갑니다. API 는 프로그램이 다른 프로그램을 부르도록 열어 둔 입구입니다. 주소는 Firebase 프로젝트마다 하나입니다. 끝은 messages:send 입니다.
POST https://fcm.googleapis.com/v1/projects/my-app/messages:send
Authorization: Bearer <액세스 토큰>
Content-Type: application/json
주소 가운데의 my-app 은 Firebase 프로젝트 이름입니다. Authorization 줄은 보내는 쪽이 누구인지 밝히는 줄입니다. 이 줄은 다음 소절에서 봅니다.
요청 본문은 JSON(JavaScript Object Notation)입니다. JSON 은 이름과 값을 중괄호로 묶어 적는 텍스트 형식입니다. 아래는 배달 출발 알림 한 건의 본문입니다.
{
"message": {
"token": "<등록 토큰>",
"notification": {
"title": "배달 출발",
"body": "곧 도착합니다"
},
"data": { "orderId": "1234" }
}
}
방금 본 본문에서 token 은 받을 앱입니다. notification 은 화면에 띄울 제목과 내용입니다. data 는 앱 코드에 넘길 값입니다. 두 칸이 어떻게 다른지는 다음 소절에서 봅니다.
보내기가 성공하면 FCM 은 projects/my-app/messages/… 꼴의 메시지 이름을 돌려줍니다. 이 답은 FCM 서버가 요청을 받았다는 뜻입니다. 기기에 닿았다는 뜻이 아닙니다.
보내는 쪽을 밝히는 액세스 토큰
아무 서버나 우리 앱 사용자에게 알림을 보내면 안 됩니다. 그래서 요청에는 이 Firebase 프로젝트의 주인이라는 증명이 붙습니다. 앞의 Authorization 줄에 실린 액세스 토큰이 그 증명입니다.
액세스 토큰은 서비스 계정으로 받습니다. 서비스 계정은 사람이 아니라 서버 프로그램에게 주는 Google Cloud 계정입니다. 백엔드는 이 계정의 비밀 키를 들고 Google 에 가서 잠깐 쓸 액세스 토큰을 받아 옵니다.
이 발급 절차는 OAuth 라는 표준을 따릅니다. OAuth 는 한 프로그램이 다른 서비스를 쓸 권한을 토큰으로 받아 오는 절차입니다.
토큰 발급과 요청 조립을 매번 손으로 짜지 않도록 Google 이 Firebase Admin SDK를 내놓았습니다. Java, Python, Node.js, Go 같은 서버 언어마다 있는 라이브러리입니다. 백엔드 코드는 메시지 객체를 만들어 send 를 부르기만 하면 됩니다.
알림 메시지와 데이터 메시지
FCM 메시지는 무엇을 싣느냐로 두 종류로 나뉩니다. 앞 본문의 notification 칸과 data 칸이 그 둘입니다. 아래 설명은 Android 앱을 기준으로 합니다.
알림 메시지는 notification 칸을 쓰는 메시지입니다. 앱이 화면 뒤로 물러나 있으면 FCM SDK 가 앱 코드를 거치지 않고 제목과 내용을 바로 화면에 띄웁니다. 사용자가 그 알림을 누르면 그때 앱이 열립니다.
데이터 메시지는 data 칸만 쓰는 메시지입니다. 화면에는 저절로 아무것도 뜨지 않습니다. 값은 앱 코드로 전해집니다. 그 값으로 무엇을 할지는 앱이 정합니다.
앱은 새 데이터를 미리 받아 두거나 알림을 직접 꾸며 띄웁니다. data 의 값은 전부 문자열로 적습니다.
아래 표는 두 종류를 앱의 상태별로 나란히 놓은 것입니다.
| 알림 메시지 | 데이터 메시지 | |
|---|---|---|
| 싣는 칸 | notification |
data |
| 앱이 화면 뒤에 있을 때 | SDK 가 화면에 바로 띄운다 | 앱 코드가 받는다 |
| 앱이 화면에 떠 있을 때 | 앱 코드가 받는다 | 앱 코드가 받는다 |
| 알맞은 쓰임 | 사람에게 보여 줄 소식 | 앱이 처리할 신호 |
표에서 갈리는 줄은 「앱이 화면 뒤에 있을 때」 하나입니다. 두 칸을 한 메시지에 같이 실을 수도 있습니다. 그러면 앱이 화면 뒤에 있을 때는 제목이 먼저 뜹니다. data 는 사용자가 알림을 눌러 앱이 열릴 때 넘어옵니다.
토픽으로 여럿에게 보내기
같은 알림을 여러 사람에게 보낼 때가 있습니다. 경기 결과를 그 팀을 고른 사용자 모두에게 알리는 경우입니다. 토큰 수만큼 요청을 보내는 대신 토픽을 씁니다.
토픽은 이름이 붙은 구독 목록입니다. 앱이 토픽 이름을 대고 구독하면 FCM 이 그 앱의 토큰을 목록에 넣습니다. 백엔드는 token 칸 대신 topic 칸에 이름을 적어 한 번만 보냅니다. FCM 이 목록의 앱 전부에게 나눠 보냅니다.
보내는 쪽이 받는 쪽을 모르고 이름만 아는 이런 방식을 발행-구독이라고 부릅니다. 백엔드는 구독자 명단을 관리하지 않아도 됩니다. 대신 누가 받았는지 알 수 없습니다. 사람마다 내용을 달리할 수도 없습니다.
우선순위와 축소 키
FCM 메시지에는 우선순위가 붙습니다. 보통과 높음 둘 가운데 하나입니다. 백엔드가 요청 본문에 칸 하나를 더 적어 고릅니다. 이 값은 휴대폰이 절전 중일 때 메시지가 언제 닿느냐를 가릅니다.
Android 휴대폰은 한동안 안 쓰면 배터리를 아끼려고 네트워크를 거의 끊는 절전 상태에 들어갑니다. Android 는 이 상태를 Doze라고 부릅니다. 보통 우선순위로 보낸 메시지는 이때 휴대폰이 잠깐 깨어나는 때까지 미뤄집니다.
높은 우선순위를 단 메시지는 절전 상태의 휴대폰도 깨워 바로 전해집니다. 걸려 오는 전화나 채팅처럼 곧바로 봐야 하는 알림에 씁니다. 사용자에게 아무것도 안 보이는 메시지에 높은 우선순위를 자꾸 달면, 운영체제가 그 앱의 메시지를 낮춰 다루기도 합니다.
휴대폰이 꺼져 있는 동안 같은 종류의 메시지가 여러 개 쌓일 수 있습니다. 「새 메일이 있다」는 신호라면 다섯 개를 다 받을 필요가 없습니다. 메시지에 축소 키(collapse key)를 달면 FCM 은 같은 키의 메시지 가운데 마지막 것만 남겨 전합니다.
도착을 약속하지 않는 전달
FCM 은 메시지를 전하려고 애쓰지만 도착을 약속하지는 않습니다. 기기가 꺼져 있으면 메시지를 한동안 들고 있다가 켜지면 전합니다. 들고 있을 기간이 지나면 버립니다.
그래서 알림을 데이터의 원본으로 쓰지 않습니다. 알림은 「새 소식이 있다」는 신호로만 씁니다. 내용은 앱이 열릴 때 백엔드에서 다시 받아 옵니다. 메신저 알림 하나를 놓쳐도 앱을 열면 메시지가 다 보이는 까닭이 이것입니다.
Google Play 서비스가 없는 Android 휴대폰에는 FCM 이 닿지 않습니다. 중국에서 파는 휴대폰 상당수가 그렇습니다. 이런 기기에 알림을 보내려면 Huawei 의 HMS Push Kit 처럼 휴대폰 제조사가 따로 운영하는 푸시 서비스를 붙여야 합니다.
FCM 을 고르지 않는 때
FCM 은 앱이 화면에 떠 있지 않을 때 소식을 전하는 길입니다. 앱이 닫혔거나 화면 뒤에 있을 때입니다. 받는 쪽의 사정이 다르면 다른 길을 씁니다.
앱이 화면에 떠 있는 동안에는 FCM 을 거치지 않아도 됩니다. 앱이 백엔드와 직접 연결을 열어 두고 주고받습니다. 채팅방 화면 안에서 오가는 메시지가 그렇습니다. 이때는 웹소켓이나 Server-Sent Events를 씁니다.
받는 쪽이 다른 회사의 서버라면 그 서버 주소로 HTTP 요청을 바로 보냅니다. 이 방식을 웹훅이라고 합니다. 서버는 늘 켜져 있고 주소가 정해져 있어서 FCM 같은 중계가 필요 없습니다.
iOS 앱만 서비스한다면 FCM 을 빼고 APNs 에 바로 보내기도 합니다. FCM 을 거치면 중계가 한 번 더 늘기 때문입니다. FCM 의 값은 Android 와 iOS 와 웹을 요청 하나로 다룬다는 데 있습니다.
맞물림
FCM 은 혼자 알림을 끝까지 나르지 않습니다. 앞 절 그림의 세 갈래와 백엔드 쪽 입구가 모두 다른 물건에 붙어서 돕니다. 이 절은 그 네 곳에서 누가 누구를 부르고, 붙인 대가로 무엇을 치르는지 봅니다.
FCM 에서 Google Play 서비스로
FCM 서버가 휴대폰의 Google Play 서비스로 메시지를 내려보냅니다. Google Play 서비스는 받을 앱을 찾아 깨웁니다. 앱 안의 FCM SDK 가 그 메시지를 앱 코드로 넘깁니다. 이렇게 전달을 한 앱이 도맡으므로 앱마다 연결을 따로 두지 않아도 됩니다.
이 길을 쓰면 Google 에 묶입니다. Google Play 서비스가 없는 휴대폰에는 이 길이 아예 없습니다.
사용자가 설정에서 앱을 강제로 멈추면, 그 앱은 다시 열릴 때까지 메시지를 받지 못합니다. 강제 멈춤은 앱을 닫거나 화면 뒤로 보낸 것과 다른 상태입니다. 사용자가 그 앱을 직접 꺼 둔 것이라 알림도 함께 막힙니다.
FCM 에서 APNs 로
iOS 앱으로 가는 메시지는 FCM 서버가 APNs 에 넘깁니다. 그러려면 개발자가 Apple 에서 받은 APNs 인증 키를 Firebase 프로젝트에 올려 둬야 합니다. FCM 은 그 키로 우리 앱 대신 APNs 에 요청합니다.
덕분에 백엔드는 Android 와 iOS 에 같은 모양의 요청을 보냅니다. 대신 APNs 의 규칙도 함께 따라옵니다.
iOS 는 화면에 아무것도 띄우지 않는 알림을 조용한 알림이라고 부릅니다. 앞에서 본 데이터 메시지가 iOS 에서는 이 조용한 알림으로 전해집니다. iOS 는 배터리를 아끼려고 조용한 알림을 늦추거나 건너뛰기도 합니다. FCM 은 이 일을 막아 주지 못합니다.
Firebase 에 올린 APNs 인증 키가 틀리면 iOS 쪽 알림만 실패합니다. Android 쪽 알림은 그대로 닿으므로 이 오류는 iOS 사용자에게서만 드러납니다.
FCM 에서 브라우저 서비스 워커로
웹 앱으로 가는 메시지는 FCM 서버가 브라우저의 푸시 서비스로 넘깁니다. 브라우저는 페이지가 닫혀 있어도 서비스 워커를 깨워 메시지를 건넵니다.
웹 쪽은 준비할 것이 늘어납니다. 사이트는 서비스 워커 파일과 FCM 웹 SDK 설정을 함께 둬야 합니다. 사용자가 브라우저에서 알림을 허락해야 받습니다. 브라우저 자체가 꺼져 있으면 받지 못합니다.
Admin SDK 에서 FCM 으로
백엔드는 Firebase Admin SDK 로 FCM 을 부릅니다. Admin SDK 는 서비스 계정의 비밀 키를 읽어 액세스 토큰을 받습니다. 그다음 메시지를 HTTP 요청으로 조립해 FCM 서버에 보냅니다. 같은 Admin SDK 로 Firebase 의 로그인이나 데이터베이스 기능도 부릅니다.
이 입구를 쓰면 비밀 키를 지켜야 합니다. 서비스 계정 키가 새면 그 키를 가진 누구나 우리 앱 이름으로 알림을 보낼 수 있습니다. 그래서 키 파일을 코드 저장소에 넣지 않고 따로 비밀 저장소에 둡니다.
관련 항목
FCM 이 메시지를 넘겨주는 중계자
Google Play 서비스 · APNs · 웹 푸시 · Push API · 서비스 워커
FCM 과 함께 Firebase 를 이루는 서비스
Firebase · Firebase Admin SDK · Cloud Firestore · Firebase Authentication · Cloud Functions · Firebase Crashlytics
FCM 메시지 한 건을 이루는 구성 요소
디바이스 토큰 · 페이로드 · 토픽 · collapse key · 메시지 우선순위 · TTL
FCM 을 부를 때 쓰는 인증 수단
서비스 계정 · OAuth 2.0 · 액세스 토큰 · API 키 · JWT
FCM 이 기대는 네트워크 원리
NAT · 지속 연결 · 킵얼라이브 · HTTP · JSON · 발행-구독
FCM 이 속하는 상위 분류
푸시 알림 · 푸시 · 모바일 개발 · Android · iOS · SDK
FCM 을 대신할 수 있는 다른 수단
Amazon SNS · OneSignal · HMS Push Kit · 웹소켓 · Server-Sent Events · 폴링 · 웹훅
FCM 을 운영할 때 부딪히는 문제
다른 이름: FCM · 파이어베이스 클라우드 메시징 · Firebase 클라우드 메시징