안드로이드 기기(A)에서 다른 기기(B)로 메시지를 보내려면
보내는 클라이언트(A) -> FCM 백엔드 -> 받는 클라이언트(B)
이 순서로 통신이 이루어진다.
보내는 클라이언트 -> FCM 백엔드에서 상호작용 하는 방식은 다음과 같은 방식이 있다.
이중에서 구글링했을 때 대부분 기존 HTTP 방식을 대부분 사용하여 설명되어있고, 나도 이 방식을 사용해왔다.
그러나 기존 HTTP 방식은 2023년 6월에 지원이 중단되어 2024년 6월까지 FCM HTTP v1 API로 마이그레이션이 필요하다.
변경점: HTTP v1 부터는 기존에 서버키를 사용하던 방식 대신 OAuth 2.0 AccessToken을 사용하고, 보내는 메시지가 변경되었다.
그래서 먼저 OAuth 2.0을 사용하여 AccessToken을 발급받기 위해 방법을 찾아봤고, Google Login API를 사용해서 구현한 내용들이 다수였다. 나의 프로젝트에서는 로그인을 사용하지 않아서 로그인 없이 발급받을 수 있는 방법을 적용한 과정을 적어서 나와 같은 문제를 가지는 개발자들이 참고할 수 있도록 글을 적어본다.
OAuth 2.0 AccessToken을 발급받을 수 있는 방식들 중 사용자 인증 정보를 사용한 방식을 사용한다.
private static String getAccessToken() throws IOException {
GoogleCredentials googleCredentials = GoogleCredentials.fromStream(new FileInputStream("service-account.json")) .createScoped(Arrays.asList(SCOPES));
googleCredentials.refreshAccessToken();
return googleCredentials.getAccessToken().getTokenValue();
}
여기서 service-account.json
, SCOPES
를 채워넣어야한다.
json 파일은 서비스 비공개 키로, 다음 과정을 통해 발급받을 수 있다.
1. 사용할 Firebase 프로젝트에서 설정 > 프로젝트 설정
새 비공개 키 생성을 클릭하고 키 생성을 클릭하여 확인.
키가 들어있는 JSON 파일 저장.
SCOPES는 공식문서 링크에 나와있는
https://www.googleapis.com/auth/firebase.messaging
를 추가한다.
위의 예시 코드를 Kotlin으로 변환하고, json파일을 asset을 사용해서 추가하고, SCOPES에 코드를 추가한 결과는 다음과 같다. (asset 가이드)
fun getAccessToken(){
val asset = resources.assets.open("파일명.json")
val googleCredential = GoogleCredential.fromStream(asset)
.createScoped(listOf("https://www.googleapis.com/auth/firebase.messaging"))
googleCredential.refreshToken()
val accessToekn = googleCredential.accessToken
}
이제 FCM 백엔드에 AccessToken을 사용해서 요청을 보낸다.
링크를 참고하여 마이그레이션을 진행한다.
val api = "https://fcm.googleapis.com/v1/projects/(projectname)/messages:send"
Firebase 프로젝트 ID를 (projectname) 부분에 입력한다.
기존에 사용하던 서버키 대신 위에서 발급받은 AccessToken을 사용한다.
val request = Request.Builder()
.url(url)
.post(body)
.addHeader("Authorization", "Bearer $accessToken")
.addHeader("Content-Type", "application/json")
.build()
링크에서 설명된 변경점에 맞춰 jsonObject를 생성한다.
val json = JSONObject().apply {
put("message", JSONObject().apply {
put("token", token)
put("notification", JSONObject().apply {
put("title", title)
put("body", content)
})
})
}
fun sendNotifications(
accessToken: String,
token: String,
title: String,
content: String
) {
val api =
"https://fcm.googleapis.com/v1/projects/(projectname)/messages:send"
val url = api.toHttpUrlOrNull()!!.newBuilder().build()
val client = OkHttpClient()
val json = JSONObject().apply {
put("message", JSONObject().apply {
put("token", token)
put("notification", JSONObject().apply {
put("title", title)
put("body", content)
})
})
}
val body =
json.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
val request = Request.Builder()
.url(url)
.post(body)
.addHeader("Authorization", "Bearer $accessToken")
.addHeader("Content-Type", "application/json")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
// Handle success
val responseBody = response.body?.string()
println("FCM Response: $responseBody")
}
})
}
참고
<받는 쪽>
Firebase.messaging.subscribeToTopic(topic)
<보내는 쪽>
FCM 백엔드에 보낼 메시지에 token대신 topic을 사용한다.
val json = JSONObject().apply {
put("message", JSONObject().apply {
put("topic", topic)
put("notification", JSONObject().apply {
put("title", title)
put("body", content)
})
})
}
만약 안드로이드 클라이언트는 서버쪽의 FCM을 받기만 한다면 수정할 필요는 없나요?