본문 바로가기
Spring Boot :

[FN] Spring Boot API / FCM Token 전송

by 밍코딩코 2025. 2. 19.

모임 신청을 하고 신청에 대한 알림을 FCM으로 모임장에게 띄워주기 위해서

React Native에서 Spring Boot 서버로 FCM 토큰을 보내주어야하는데 로컬호스트:포트 번호로 연결할 수는 있지만,

다른 기기나 환경에서 실행하려면 문제가 생길 것 같아서 EC2에 Spring Boot API를 배포하고 EC2의 탄력적 IP를 React Native에서 지정해서 FCM 토큰을 전송하기로 하였다.

 

1. Spring Boot에 FCM 토큰을 받는 API 엔드 포인트 생성

FcmController.java

  • RestController : HTTP 요청을 처리하는 RESTful 웹 서비스 컨트롤러 클래스를 정의
  • PostMapping : 클라이언트에서 POST 요청을 /api/fcm-token 경로로 보냈을 때 이 메서드가 호출됨
  • 토큰을 받으면 받은 토큰 정보를 출력하도록.
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FCMController {

    @PostMapping("/api/fcm-token")
    public String receiveFCMToken(@RequestBody String token) {
        
        System.out.println("Received FCM Token: " + token);
        return "Token received successfully!";
    }
}

 

추가로 Firebase에서 프로젝트 - 프로젝트 설정 - 서비스 계정에서 새 비공개 키를 생성해서 해당 json 파일을 src\main\java\resources 파일에 저장해준다.

 

FirebaseConfig.java

  • 그리고 저장했던 Firebase 서비스 계정 키 파일 경로를 지정해주고, Firebase admin sdk를 사용하기 위한 초기화 코드를 작성해준다.
package com.example.festanow_meeting;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.io.InputStream;

@Configuration
public class FirebaseConfig {

    @Bean
    public FirebaseApp firebaseApp() throws IOException {
        
        InputStream serviceAccount = new ClassPathResource("project-adminsdk.json").getInputStream();

        FirebaseOptions options = new FirebaseOptions.Builder()
                .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                .build();

        return FirebaseApp.initializeApp(options);
    }
}

 

build.gradle에 Firebase Admin SDK 의존성 추가하기

implementation 'com.google.firebase:firebase-admin:9.2.0'

 

2. React Native에서 앱 실행 시 FCM 토큰을 Spring Boot 서버로 전달

  • App.tsx 파일에 토큰을 받아오고 서버로 전송하는 코드를 추가하였다.
  • 임의로 로컬 IPv4 주소를 사용하였고, 해당 기능이 모두 구현되면 EC2 인스턴스에 API 배포 후 IP 주소는 탄력적 IP주소로 변경해 주어야 한다.
  • 토큰을 불러오면서 Firestore database users 컬렉션에 fcmToken값이 저장되도록 하였다.
  • 모임장의 fcmToken 값으로 푸쉬 알림 보내기 구현해야하기 위해
import axios from 'axios';
import messaging from '@react-native-firebase/messaging';

const getFcmToken = async () => {
    try {
      const fcmToken = await messaging().getToken();
      console.log('FCM Token:', fcmToken);
      
      const currentUser = auth().currentUser;
      if (currentUser) {
        await firestore().collection('users').doc(currentUser.uid).set(
          {
            fcmToken: fcmToken, // FCM 토큰 저장
          },
          { merge: true } 
        );
        console.log('FCM 토큰 Firestore에 저장 성공');
      }

      
      await axios.post('http://IPv4주소:포트번호/api/fcm-token', {
        token: fcmToken,
      });

      console.log('FCM 토큰 서버로 전송 성공');
    } catch (error) {
      console.error('FCM 토큰 가져오기 또는 서버 전송 실패:', error);
    }
  };

 

추가로 현재는 안드로이드만 개발 중이지만 나중을 위해 IOS에서 권한 요청과 토큰을 가져오는 코드도 추가해두었다

 

const requestUserPermission = async () => {
    const authStatus = await messaging().requestPermission();
    const enabled =
      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
      authStatus === messaging.AuthorizationStatus.PROVISIONAL;

    if (enabled) {
      console.log('알림 권한 허용됨');
      getFcmToken();
    } else {
      console.log('알림 권한 거부됨');
    }
  };

 

여기까지 했으면 토큰이 잘 보내지는지 테스트를 해보자.

Spring Boot 서버 실행 - React Native (npm start) - 안드로이드 스튜디오 에뮬레이터

VSCode 콘솔 메시지
Intellij 메시지

정상적으로 FCM 토큰이 전달되는 것을 확인할 수 있다.

 

추가로 게시글을 작성한 모임장의 FCM 토큰이 주기적으로 변경되는 점을 고려해서 업데이트된 토큰을 Firestore 유저 컬렉션의. set으로 덮어씀으로써 최신화 되도록 App.tsx에 코드를 추가하였다.

import firestore from '@react-native-firebase/firestore';
import auth from '@react-native-firebase/auth';

const updateFCMToken = async () => {
    const fcmToken = await messaging().getToken();
    const currentUser = auth().currentUser;
  
    if (currentUser) {
      await firestore().collection('users').doc(currentUser.uid).set({
        fcmToken: fcmToken, // 기존 토큰에 덮어씀
      });
    }
  };
  
  messaging().onTokenRefresh(updateFCMToken);