- 클라이언트와 통신
- FCM 서버에 올바르게 형식이 지정된 메시지 요청 전송
- 지수 백오프를 사용하여 요청을 처리하거나 재전송 할 수 있음
- 서버 키와 클라이언트 등록 토큰을 안전하게 저장
- 클라이언트 코드에 서버 키는 포함시키지 않을 것
- XMPP의 경우 전송하는 각 메시지를 고유하게 구별하기 위해 서버에서 메시지 ID를 생성할 수 있어야 합니다.
- FCM HTTP 연결 서버는 메시지 ID를 생성하여 응답에서 반환합니다.
- XMPP 메시지 ID는 발신자 ID별로 고유해야 합니다.
- Admin SDK
- 원시 프로토콜
- FCM HTTP v1 API는 가장 최신 프로토콜로서 보다 안전한 인증과 유연한 교차 플랫폼 메시징 기능을 제공
- 이전의 HTTP 및 XMPP 서버 프로토콜 영역도 사용 가능
클라이언트 애플리케이션에서 업스트림 메시징을 사용하려면 XMPP를 사용해야 함
- node.js로 앱서버 구축시는 Admin Node.js SDK의 FCM API를 사용하여 메시지를 보내야 함
-
업스트림/다운스트림 메시지
- HTTP: 클라우드에서 기기로 전송하는 다운스트림 전용
- XMPP: 기기에서 클라우드로 전송하는 업스트림 및 클라우드에서 기기로 전송하는 다운스트림
-
메시징(동기 또는 비동기)
- HTTP: 동기 메시징입니다. 앱 서버가 HTTP POST 요청으로 메시지를 보내고 응답을 기다립니다. 이 방식은 동기 메시징이며 응답이 수신될 때까지 발신자가 다른 메시지를 보내지 못하도록 차단합니다.
- XMPP: 비동기 메시징입니다. 앱 서버가 영구 XMPP 연결을 통해 최대 회선 속도로 모든 기기에서 양방향으로 메시지를 주고받습니다. XMPP 연결 서버가 확인 또는 실패 알림을 비동기 방식으로 보냅니다. 확인 형식은 JSON으로 인코딩된 특수한 ACK 및 NACK XMPP 메시지입니다.
-
JSON
- HTTP: JSON 메시지는 HTTP POST로 전송됩니다.
- XMPP: JSON 메시지는 XMPP 메시지에 캡슐화됩니다.
-
일반 텍스트
- HTTP: 일반 텍스트 메시지는 HTTP POST로 전송됩니다.
- XMPP: 지원되지 않습니다.
-
여러 등록 토큰에 전송되는 멀티캐스트 다운스트림
- HTTP: JSON 메시지 형식으로 지원됩니다.
- XMPP: 지원되지 않습니다.
메시지를 보내려면 앱 서버에서 JSON 키-값 쌍으로 구성된 HTTP 헤더와 HTTP 본문을 포함하는 POST 요청을 만듭니다. 헤더 및 본문의 옵션에 관한 자세한 내용은 앱 서버 보내기 요청 작성을 참조
-
액세스 토큰을 통한 보안 향상
- HTTP v1 API는 OAuth2 보안 모델에 따라 수명이 짧은 액세스 토큰을 사용합니다. 액세스 토큰이 공개되는 경우에도 만료되기 전에 1시간 정도만 악의적으로 사용될 수 있습니다. 새로고침 토큰이 이전 API에서 사용하는 보안 키만큼 자주 전송되지 않으므로 캡처될 가능성이 매우 낮습니다.
-
여러 플랫폼에서 보다 효율적인 메시지 맞춤설정
- 메시지 본문의 경우 HTTP v1 API에 모든 대상 인스턴스에 전달되는 공용 키는 물론 여러 플랫폼의 메시지를 맞춤설정할 수 있는 플랫폼별 키가 있습니다. 이러한 키를 사용하면 메시지 하나로 여러 클라이언트 플랫폼에 약간 다른 페이로드를 전송하는 ‘재정의’를 만들 수 있습니다.
-
새 클라이언트 플랫폼 버전을 위한 확장성 강화 및 미래 경쟁력 확보
- HTTP v1 API는 iOS, Android, 웹에 제공되는 메시지 옵션을 완전히 지원합니다. 각 플랫폼마다 JSON 페이로드에 자체 정의된 블록이 있으므로 FCM에서 필요에 따라 새 버전과 새 플랫폼으로 API를 확장할 수 있습니다.
- 기기 그룹 메시징이나 멀티캐스트 메시징을 사용하는 앱은 다음 버전의 API를 기다리는 것이 좋습니다.
- HTTP v1은 이전 API의 해당 기능을 지원하지 않습니다.
- 기존 프로토콜 참고
- 서비스 계정을 인증하고 Firebase 서비스에 액세스하도록 승인하려면 JSON 형식의 비공개 키 파일을 생성하고 이 키를 사용하여 수명이 짧은 OAuth 2.0 토큰을 발급받아야 합니다.
- 유효한 토큰을 확보했으면 원격 구성, FCM 등 다양한 Firebase 서비스의 요구사항에 따라 서버 요청에 토큰을 추가할 수 있습니다.
- 다른 서비스 계정을 사용하는 경우 편집자 또는 소유자 권한이 있어야 합니다.
- Firebase 콘솔에서 설정> 서비스 계정을 열기.
- 새 비공개 키 생성을 클릭하고 키 생성을 클릭하여 확인.
- 키가 들어있는 JSON 파일을 안전하게 저장.
- 엑세스 토큰을 발급받기 위해서 비공개 키 Json은 필수입니다.
-
토큰을 발급받으려면 사용할 언어에 대한 Google API 클라이언트 라이브러리를 사용하여 다음과 같이 비공개 키 JSON 파일을 참조합니다.
-
코드
private static String getAccessToken() throws IOException { GoogleCredential googleCredential = GoogleCredential .fromStream(new FileInputStream("service-account.json")) .createScoped(Arrays.asList(SCOPES)); googleCredential.refreshToken(); return googleCredential.getAccessToken(); }
-
service--account.json에 Firebase console에서 받은 json 파일을 입력합니다. 꽤나 헤매던 부분인데, SCOPES에 대한 언급을 찾을 수 없었습니다. 답은 아래 url을 참고하시면 됩니다. https://stackoverflow.com/questions/47325150/where-to-get-scopes-dependencies-for-java-for-new-firebase-cloud-message-api
-
세팅 url
-
AccessToken은 보안을 위해 주기적으로 바뀌는데, 위 소스를 적용하면 토큰이 만료되면 토큰 새로고침 메소드가 자동으로 호출되어 업데이트된 토큰이 발급됩니다.
- Authorization 헤더의 값으로 토큰을 Authorization: Bearer <access_token> 형식으로 추가합니다.
URL url = new URL(FCM_SEND_ENDPOINT);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestProperty("Authorization", "Bearer " + getAccessToken());
httpURLConnection.setRequestProperty("Content-Type", "application/json; UTF-8");
return httpURLConnection;
- 여기까지 잘 됐다면 보낼 준비가 끝난 상태입니다. 아직 프로그램을 구동시켜보지 못하겠지만, 다음 단계에서 curl을 이용하여 아래와 같이 테스트 해 볼 수 있습니다.
앱 서버 보내기 요청 작성 보내기 요청의 전송 유형
주제 이름 조건 기기 등록 토큰 기기 그룹 이름(기존 프로토콜만 해당) 자세한 내용은 메시지 유형을 참조 바랍니다.
send endpoint는 다음과 같이 구성됩니다.
POST https://fcm.googleapis.com/v1/projects/[myproject-name]/messages:send 자신의 endpoint url은 Firevase 콘솔의 일반 프로젝트 설정 탭에서 확인할 수 있습니다. 단지, 프로젝트 ID만 알고 있으면 됩니다.
특정 기기에 메시지 전송 메세지 전송은 https POST로 전송합니다.
curl -X POST -H "Authorization: Bearer ya29.c.El7uBYyvqs1..." -H "Content-Type: application/json" -d
'{
"message":{
"notification": {
"title": "FCM Message",
"body": "This is an FCM Message",
},
"token": "fh1Ego6mhk0:APA91bF..."
}
}' "https://fcm.googleapis.com/v1/projects/my-fcm-project/messages:send"
Authorization에 access token을 넣어줍니다. message.token 은 클라이언트 사용자의 등록 토큰입니다. 만일 클라이언트가 준비되어 있다면 거기서 생성된 등록 토큰을 넣어주면 됩니다.
정상적으로 전송되면 아래와 같은 응답이 오게 됩니다.
{
"name": "projects/myproject-b5ae1/messages/0:1500415314455276%31bd1c9631bd1c96"
}
요청 본문에 대해 보다 자세한 사항은 FCM 메시지 정보 또는 API 참조를 확인하세요
코드 작성 FCM Server Key를 받았다면 Access Token을 다음과 같이 받아올 수 있습니다. 헌데, 이 작업은 구글 API 클라이언트 라이브러리를 사용합니다.
따라서 maven 의존성을 아래와 같이 설정해 줍니다.
<project>
<dependencies>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.23.0</version>
</dependency>
</dependencies>
</project>
access token 을 받아오기 위한 클래스를 아래와 같이 정의합니다.
package com.mysite.fcm.manager;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class AccessToken
{
public static String getAccessToken(String keyFile, List<String> scopes) throws IOException
{
InputStream resourceAsStream = AccessToken.class
.getResourceAsStream("/" + keyFile);
GoogleCredential googleCredential = GoogleCredential
.fromStream(resourceAsStream)
.createScoped(scopes);
googleCredential.refreshToken();
return googleCredential.getAccessToken();
}
}
Firebase 예제들은 dependency, import를 명시적으로 알려주지 않더군요. 이런거 찾다가 시간 다 보낸다는…. 😭
메세지를 보내기 위한 클래스를 작성합니다. 지저분한 코드는 나중에 정리.. 안되어 있으면 알아서 보시길~
package com.mysite.fcm.manager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class Push
{
String endpoint;
List<String> scope;
String keyFile;
String accessToken;
HttpURLConnection http;
StringBuffer responseBody;
public Push() throws IOException
{
this.endpoint = System.getProperty("endpoint");
this.keyFile = System.getProperty("fcm_key");
String tmp[] = System.getProperty("scope").split(",");
this.scope = new ArrayList<>();
for (String s : tmp)
{
this.scope.add(s);
}
this.accessToken = AccessToken.getAccessToken(keyFile, scope);
responseBody = new StringBuffer();
}
public String getEndpoint()
{
return endpoint;
}
public void setEndpoint(String endpoint)
{
this.endpoint = endpoint;
}
public String getAccessToken()
{
return accessToken;
}
public void setAccessToken(String accessToken)
{
this.accessToken = accessToken;
}
public StringBuffer getResponseBody()
{
return responseBody;
}
public String send(String userToken, String message) throws IOException
{
URL url = new URL(endpoint);
http = (HttpURLConnection) url.openConnection();
http.setRequestMethod("POST");
http.setDoInput(true);
http.setRequestProperty("Authorization", "Bearer " + accessToken);
http.setRequestProperty("Content-Type", "application/json; UTF-8");
http.setDoOutput(true);
OutputStream os = http.getOutputStream();
String body =
"{\n" + "\"message\":{\n" + " \"notification\": {\n" + " \"title\": \"FCM Message\",\n"
+ " \"body\": \"" + message + "\",\n"
+ " },\n" + " \"token\": \"" + userToken + "\"\n" + " }\n" + "}\n";
System.out.println(body);
os.write(body.getBytes());
os.flush();
os.close();
System.out.println("* CODE : " + http.getResponseCode());
System.out.println("* MSG : " + http.getResponseMessage());
if(http.getResponseCode() == 200)
{
BufferedReader br = new BufferedReader(new InputStreamReader(http.getInputStream(), "UTF8"));
String line;
while ((line = br.readLine()) != null)
{
responseBody.append(line);
}
br.close();
}
http.disconnect();
return http.getResponseMessage();
}
}
이제 push main만 만들면 된다.
public static void main(String[] args) throws IOException
{
Push push = new Push();
try
{
push.send("fPmC2-5G8dU:APA...(client 등록 토큰 입력)", "잘 갑니다~~");
} catch (IOException e) {
e.printStackTrace();
}
}
실행해 보니 잘 가네요.
문제 해결 처음 보낼 때, 403 에러가 뜨면서 아래와 같이 리턴되는 경우가 있다.
{
"error": {
"code": 403,
"message": "Firebase Cloud Messaging API has not been used in project 42759??????? before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/fcm.googleapis.com/overview?project=42759??????? then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
"status": "PERMISSION_DENIED",
"details": [
{
"@type": "type.googleapis.com/google.rpc.Help",
"links": [
{
"description": "Google developers console API activation",
"url": "https://console.developers.google.com/apis/api/fcm.googleapis.com/overview?project=42759???????"
}
]
}
]
}
}