Spring Boot
와 Firebase FCM
을 이용해, PushNotification Server 구축
을 해보자.
현재 Front는 Android 측에서 구현이 되어있기 때문에, 이번 포스팅에서는 BackEnd 구축
만 다루겠다.
유저의 device에 push 알림을 보내기 위한 구현은 모바일 os마다 다르겠지만 큰 틀은 비슷하다.
우선 용어를 정리하자
Notification Server
Client App
Provider
Client App을 Notification Server에 등록
Client App을 켜면 각각의 Client App을 구분하는 Token을 Notification Server에서 발급
Client App에서 Token을 Provider로 전송
Provider는 Token 저장
Client App에 알림 전송 필요시, 토큰 값과 함께 Notification Server에 요청
Client App에서 Push 알림 수신
빠른 구축을 위해 Token을 저장하는 과정을 생략하고, Front(Android)에서 생성된 토큰 값을 console에 출력하여,
그 토큰을 바로 사용하겠다.
Firebase 프로젝트 개요 (톱니바퀴 클릭
) 👉 서비스 계정
👉 새 비공개 키 생성
클릭
resources/firebase/
하위에 비공개 키를 넣자. <!--firebase sdk-->
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>8.1.0</version>
</dependency>
<!--okhttp-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.net.HttpHeaders;
import lombok.RequiredArgsConstructor;
import okhttp3.*;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
@Component
@RequiredArgsConstructor
public class FirebaseCloudMessageService {
private final String API_URL = "https://fcm.googleapis.com/v1/projects/본인 프로젝트 ID/messages:send";
private final ObjectMapper objectMapper;
public void sendMessageTo(String targetToken, String title, String body) throws IOException {
String message = makeMessage(targetToken, title, body);
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message, MediaType.get("application/json; charset=utf-8"));
Request request = new Request.Builder()
.url(API_URL)
.post(requestBody)
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken())
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8")
.build();
Response response = client.newCall(request)
.execute();
System.out.println(response.body().string());
}
private String makeMessage(String targetToken, String title, String body) throws JsonProcessingException {
FcmMessage fcmMessage = FcmMessage.builder()
.message(FcmMessage.Message.builder()
.token(targetToken)
.notification(FcmMessage.Notification.builder()
.title(title)
.body(body)
.image(null)
.build()
)
.build()
)
.validate_only(false)
.build();
return objectMapper.writeValueAsString(fcmMessage);
}
private String getAccessToken() throws IOException {
String firebaseConfigPath = "/firebase/다운 받은 비공개키.json";
GoogleCredentials googleCredentials = GoogleCredentials
.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())
.createScoped(List.of("https://www.googleapis.com/auth/cloud-platform"));
googleCredentials.refreshIfExpired();
return googleCredentials.getAccessToken().getTokenValue();
}
}
GoogleCredentials
GoogleApi를 사용하기 위해서 oauth2를 이용해 인증한 대상을 나타내는 객체
GoogleCredentials의 static 메서드인 fromStream()
권한 지정
이는 다시 반환된 GoogleCredentials 인스턴스의 createScoped를 통해 설정
googleCredentials.refreshIfExpired();
return googleCredentials.getAccessToken().getTokenValue();
권한 부여된 Oauth2 AccessToken 받기
받은 AccessToken은 RestAPI FCM Push 요청 시 사용
sendMessageTo()
매개변수로 전달받은 targetToken에 해당되는 device로 FCM push 전송 요청
makeMessage()
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@Builder
@AllArgsConstructor
@Getter
public class FcmMessage {
private boolean validate_only;
private Message message;
@Builder
@AllArgsConstructor
@Getter
public static class Message {
private Notification notification;
private String token;
}
@Builder
@AllArgsConstructor
@Getter
public static class Notification {
private String title;
private String body;
private String image;
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class RequestDTO {
private String title;
private String body;
private String targetToken;
}
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
@RequiredArgsConstructor
public class FcmController {
private final FirebaseCloudMessageService firebaseCloudMessageService;
@PostMapping("/api/fcm")
public ResponseEntity pushMessage(@RequestBody RequestDTO requestDTO) throws IOException {
System.out.println(requestDTO.getTargetToken() + " "
+ requestDTO.getTitle() + " " + requestDTO.getBody());
firebaseCloudMessageService.sendMessageTo(
requestDTO.getTargetToken(),
requestDTO.getTitle(),
requestDTO.getBody());
return ResponseEntity.ok().build();
}
}
targetToken, title, body를 작성해서 firebase 서버에 전송
서버는 firebase 서버에 메시지 푸시 요청
토큰 확인 시, firebase 서버는 클라이언트(Android)에게 메시지 푸시
정상 동작!
https://firebase.google.com/docs/admin/setup?hl=ko
admin-sdk사용하는 것과 이렇게 직접 api 호출중 뭐가 나은지 혹시 알고 계신가요? 구글문서에서는 admin sdk만 있는 것 같더라구요