[iOS] iOS 푸시알림(APNS, FCM)
이전에 로컬에서 보내는 푸시는 구현한 경험이 있었지만 FCM이나 APNS를 사용한 경험이 없어 조금 생소하게 다가왔다. FCM을 사용해도 애플 개발자 계정에서 APN을 설정해서 인증서나 토큰을 등록해야했는데 이 과정을 학습해보고자 한다.
푸시 종류
1. 로컬에서 직접 보내는 알림
2. 서버에서 보내는 알림
내가 사용한 FCM은 2번에 속한다.
APNS란?
Apple Push Notification Service로 iOS에서 푸시 알림을 보내기 위해 APNS를 사용해야한다.
서버에서 보내는 알림의 경우 꼭 이 APNS를 거쳐야한다.
FCM을 사용할 때 파이어베이스에서 알림을 보낸다고 착각할 수 있는데 사실 APNS로 알림을 보내는거고 실제 알림은 APNS로부터 오게된다.
앱을 시작할 때 아래 코드를 통해 APN와 통신할 수 있다. ( https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns)
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UIApplication.shared.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken
deviceToken: Data) {
self.sendDeviceTokenToServer(data: deviceToken)
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError
error: Error) {
// Try again later.
}
이런식으로 푸시 알림 콘솔을 사용하여 알림테스트가 가능하다.
https://icloud.developer.apple.com/dashboard/home/teams/68P2WV4DR3
토큰 기반 연결 / 인증서 기반 연결
토큰 기반 (p8)
Establishing a token-based connection to APNs | Apple Developer Documentation
Secure your communications with Apple Push Notification service (APNs) by using stateless authentication tokens.
developer.apple.com
토큰 기반 인증은 stateless한 방법으로 통신하여 Provider 서버와 관련된 인증서 또는 기타 정보를 조회하기 위해 APN이 필요하지 않기 때문에 인증서 기반 통신보다 빠르다.
- 여러 Provider 서버에서 동일한 토큰을 사용할 수 있다.
- 하나의 토큰을 사용하여 회사의 모든 앱에 대한 알림을 배포할 수 있다.
- 각 요청에 토큰이 포함되어 있다. (인증서 기반 요청보다 살짝 크다.)
- Apple에서 제공하는 Provider 토큰 서명키를 사용하여 적어도 한 시간에 한 번 토큰을 업데이트 하고 암호화 해야한다.
- 텍스트 파일(.p8)로 지정된 인증 토큰 서명키이다.
인증서 기반 (p12)
Establishing a certificate-based connection to APNs | Apple Developer Documentation
Secure your communications with Apple Push Notification service (APNs) by installing a certificate on your provider server.
developer.apple.com
Provider 인증서를 사용하여 Provider 서버와 APN간 보안연결을 설정한다. 개발자 계정을 통해 Apple에서 이 인증서를 얻을 수 있다.
서버 수준에서 신뢰가 설정되기 때문에 개발 알림 요청에서는 페이로드와 장치 토큰만 포함된다. (토큰기반과 달리 토큰은 포함되지 않는다.)
인증서를 사용하여 단일앱에 서비스 알림을 보낼 수 있으므로 여러 앱에 원격 알림을 보내려면 각 앱에 대해 별도의 인증서를 만들어야 하고, 각 앱의 알림에 대해 별도의 APN 연결을 관리해야한다.
- 인증서는 1년간 유효하며 APN과 계속 통신하려면 업데이트 해야한다.
1. TLS(전송 계층 보안)을 사용하여 보안 연결을 요청한다.
2. APN은 Provider 서버의 유효성을 검사할 인증서를 통해 응답한다.
3. Provider는 해당 인증서의 유효성을 검사한 후 인증서를 APN로 다시 보낸다.
4. APN은 유효성을 검사하고 보안 연결을 완료한다.
5. 이후 APN에 원격 알림 요청을 보낼 수 있다.
FCM 동작 원리
1. 앱을 시작하면 APNs로부터 나의 Device Token을 받는다.
2. APNs로부터 받은 Device Token을 받는다.
3. 받은 Device Token을 Firebase로 전송한다.
4. firebase에 fcm토큰을 요청한다.
5. firebase로부터 fcm토큰을 받는다.
6. 받은 fcm토큰을 나의 Push 서버로 전송한다. (서버는 db에 이 토큰을 저장한다.)
-> Push 서버는 전달받은 토큰을 특정 사용자들에게 푸시를 전송하는데 사용한다. (토큰을 통해 나의 앱을 설치한 사용자를 알 수 있음)
7. 푸시알림을 보내기 위해 Push 서버는 파이어베이스에 토큰과 메시지 데이터를 전송한다.
8. 파이어베이스는 서버로부터 전달받은 토큰을 통해 어떤 앱인지 판별하여 중간다리 역할인 APNs로 전송한다.
9. APNs는 사용자에게 푸시 알림을 전송한다.
Device Token이란?
푸시가 전송되는 App의 주소이다.
Device Token은 토큰은 애플에서 정한 고유한 식별자를 포함한 NSData 형태로 APNs에서만 해독이 가능하다.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
위 함수를 통해 파이어베이스에 apnsToken을 전송하였다. 그리고 파라미터 데이터 타입에서 확인할 수 있듯이 Data 타입이다.
그렇게 때문에..직접 해독은 애플에서 가능한 것 같다.
처음에 Device Token도 나오고 FCM Token도 나와서 두개가 어떤 용도로 다르게 쓰이지..?? 했는데 각각의 쓰임새가 다르다. 나는 DeviceToken은 애플에서 사용하는 앱의 주소이고, FCM Token은 파이어베이스에서 사용하는 어떤 앱에 대한 사용자의 고유한 토큰이라고 이해했다.
좀 헷갈리긴 하는데 break point 찍어서 확인해보니 FCM Token을 전송하는 부분보다 Firebase에 APNs 토큰을 전송하는 부분이 더 먼저 실행되길래 이 부분이 사전 작업이라고 이해했다. ㅠㅠ
Payload
Generating a remote notification | Apple Developer Documentation
Send notifications to the user’s device with a JSON payload.
developer.apple.com
페이로드란 전송되는 데이터를 의미한다. 위에서 푸시 알림을 보내기 위해 Push서버가 Firebase로 Token과 메시지 페이로드를 전송한다고 했는데 요기서 사용하는 페이로드를 알아보고자 한다.
공식문서에 있는 사진 줍줍해봤는데,, 나머지는 그림만 봐서 바로 알 것 같고, Silent는 아무것도 없어 설명을 보태보자면..
+) Silent Push
Silent라는 단어의 의미에서 알 수 있듯이 조용한 알림.. 사용자에게 알리지 않고 알림을 보내고, 백그라운드 알림을 사용할 때 사용한다.
Pushing background updates to your App | Apple Developer Documentation
Deliver notifications that wake your app and update it in the background.
developer.apple.com
위 링크에서 알 수 있듯 백그라운드 푸시는 일반적인 알림과 다르다.
푸시 알림은 사용자에게 뭔가를 알리고 싶을때 사용한다고만 생각했는데 백그라운드 푸시 알림은 사용자가 알림을 확인하는데 중점이 아니라 푸시 알림을 통해 application:didReceiveRemoteNotification:fetchCompletionHandler:(사용자가 푸시 알림을 클릭했을때 실행되는 메서드로 silent 푸시의 경우 사용자가 알림을 클릭하지 않아도 자동으로 실행된다.)를 실행함으로써 앱에 추가 작업을 해 줄 수있다. 일부 콘텐츠를 다운로드 하거나, 동기화, 업데이트 등.. 사용자가 푸시를 수신하는게 목적이 아니기 때문에 시스템이서 전달을 보장하지 않고, 시간당 개수도 제한되어 있다.
다시 돌아와서 이 페이로드 데이터는 JSON 형태여야하는데, VoIP(Voice over Internet Protocol) 알림의 경우 최대 페이로드의 크기는 5KB(5120byte)이고, 다른 모든 원격 알림의 경우 최대 페이로드 크기는 4KB(4096byte)이다.
푸시알림 자체에 많은 내용을 담으면 안된다는 의미로도 해석 가능하다.
{
"aps" : {
"alert" : {
"title" : "Game Request",
"subtitle" : "Five Card Draw",
"body" : "Bob wants to play poker"
},
"category" : "GAME_INVITATION"
},
"gameID" : "12345678"
}
애플 공식문서에서 제공하는 페이로드 예시이다.
이런식으로 작성하면 사용자에게 푸시 알림 전송이 가능하다. 위에서 언급한 slient를 포함해 sound, badge도 페이로드를 어떻게 작성하냐에 따라 다양한 방식으로 동작하게 할 수 있다.
나는 FCM을 사용하여 푸시를 전송하는 방식을 사용했기 때문에 Token과 payload를 Firebase로 전송해야하는 상황이였다.
https://firebase.google.com/docs/cloud-messaging/http-server-ref?hl=ko
Firebase 클라우드 메시징 HTTP 프로토콜
firebase.google.com
해당 링크에서 페이로드를 어떻게 작성해야하는지 확인할 수 있다.
파이어베이스 푸시 테스트 방법
https://fcm.googleapis.com/fcm/send
해당 주소로 페이로드를 작성해 요청을 날리면 나의 앱으로 FCM을 이용한 푸시 알림을 테스트 할 수 있다.
나는 Postman으로 푸시 알림 테스트를 진행했다.
- 주소: https://fcm.googleapis.com/fcm/send
- HTTP 메소드: Post
- Header
- Content-Type: application/json
- Authorization: key=ServerKey
- Body:
{
"notification": {
"badge": 7,
"title": "제목",
"body": "푸시 알림 내용",
"sound": "alarm.wav"
},
"to": "FCM Token",
"data": {
"url": "https://www.naver.com"
},
"type": "notice",
"content-available": "1"
}
나는 위와 같이 작성하여 푸시 알림을 전송하였다.
FCM 토큰은 아래와 같이 작성하면 콘솔창에서 확인할 수 있다.
extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
guard let fcmToken = fcmToken else { return }
print("[FCM Token]: \(fcmToken)")
}
}
제대로 전송하면 success의 값이 1로 나오고, 정상적으로 푸시 알림이 온다.
푸시 알림을 클릭하면
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
userNotificationCenter(_:didReceive:withCompletionHandler:)
와 같은 함수가 호출되는데 iOS 10 이상부터는 아래 userNotificationCenter를 통해 처리하라고 권장한다고 해서 아래 함수를 통해 푸시 알림 클릭에 대한 동작을 처리해주었다.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
// 데이터 파싱하여 사용
}
이런식으로 온 데이터는 JSON이기 때문에 딕셔너리 형식으로 변환되어 사용할 수 있다.
필요한 데이터를 내가 원하는 형식으로 파싱하여 사용하면된다.!
배운 내용을 최대한 정리하려고 했는데 좀 조잡해졌기도 하고 부족한 내용도 많지만.. 다음에 또 다른 방식으로 푸시 알림을 사용하게 되면 해당 게시글에 내용을 덧붙여야겠다!