[Spring Boot] FCM으로 푸시 알림 구현하기

왔다 정보리·2024년 4월 6일
2

FCM (Firebase Cloud Messaging)


FCM (Firebase Cloud Messaging)

메세지를 무료로 전송할 수 있는 크로스 플랫폼 메세징 솔루션.
FCM을 통해 디바이스에 푸시 알림을 보낼 수 있다.

FCM 동작 과정

FCM 동작 과정

주요 기능

  1. 사용자에게 표시되는 알림 메세지 또는 데이터 메세지 전송하기
  2. 클라이언트 앱에 메세지 배포하기
  3. 클라이언트 앱에서 서버로 메세지 전송하기

FCM 토큰

FCM Token : 사용자 기기 구분을 위한 토큰
디바이스마다 고유하다. FCM 토큰을 사용해서 어떤 기기에 알림을 보내야 하는지 구분한다. 프론트가 해당 기기의 FCM 토큰을 얻어서 서버에 보내줘야 한다. 보통 로그인하거나 앱을 실행할 때 서버에 FCM 토큰을 보내주면 된다고 한다.

Access Token : 서버에 인증하기 위해 사용하는 토큰
다음은 FCM 서버로부터 AccessToken을 얻을 수 있는 코드이다. 코드에 나와있는 key 파일은 이후에 더 자세히 다뤄보겠다.

FileInputStream serviceAccount =
new FileInputStream("path/to/serviceAccountKey.json");
    
FirebaseOptions options = new FirebaseOptions.Builder()
	.setCredentials(GoogleCredentials.fromStream(serviceAccount))
    .build();
    
FirebaseApp.initializeApp(options);

FCM 토큰이 변경되는 경우

앱이 설치된 디바이스를 추가하거나 삭제할 때 토큰이 변경될 수 있다

  • 앱이 인스턴스 ID를 삭제한 경우
  • 앱이 새 기기에서 복원된 경우
  • 사용자가 앱을 제거하거나 재설치한 경우
  • 사용자가 앱 데이터를 지운 경우

구현 방법


1. 파이어베이스 프로젝트 생성

먼저 파이어베이스 콘솔에 들어가서 회원가입을 진행한다. 회원가입을 완료했으면 파이어베이스 프로젝트에 가서 새로운 프로젝트를 만들어준다.

새로운 프로젝트 생성

프로젝트가 생성되었으면, 프로젝트에 들어가서 테스트를 위한 앱을 만들어준다.

앱을 만들기 전에 프로젝트가 먼저 생성되어 있어야 한다.
나는 안드로이드 앱으로 FCM 테스트를 진행할 예정이었기 때문에 안드로이드 앱을 만들어주었다.

앱 추가

안드로이드 프로젝트 자체에서 SHA 키를 확인할 수가 없어서 터미널에 명령어를 치는 방법으로 대체했다. 아래 명령어를 치면 터미널에 SHA 키가 나온다.

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

앱을 등록한 후 google-services.json을 다운받아 지정된 경로에 넣어준다.
google-services.json 다운

google-services.json을 넣어줬으면, build.gradle에 Firebase SDK를 추가한다.

다음은 프로젝트 수준 build.gradle,

id 'com.google.gms.google-services' version '4.4.1' apply false

다음은 모듈 수준 build.gradle이다.

plugins {
	id 'com.android.application'
    id 'com.google.gms.google-services'
}
        
dependencies {
	implementation platform('com.google.firebase:firebase-bom:32.8.0')
    implementation 'com.google.firebase:firebase-analytics'
}

앱이 만들어졌다!
프로젝트 생성 완료

2. 테스트용 안드로이드 코드 작성

파이어베이스 앱을 다 만들었으면 안드로이드 스튜디오에 푸시 알림을 처리할 수 있는 코드를 작성해주면 된다.

먼저 FirebaseMessagingService를 상속받은 클래스를 만든다. FCM 서버에서 앱에 푸시 알림을 보내면 FirebaseMessagingService에서 자동으로 처리해준다. 이 클래스 안에서 푸시 알림에 대한 로직을 짜면 된다.

public class MyFirebaseMessagingService extends FirebaseMessagingService {
	@Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
    	Log.d("FCM TEST", "알림 받기 성공");
        
        // 메시지에 포함된 데이터 처리
        Map<String, String> data = remoteMessage.getData();
        
        // Notification 타입의 메시지인 경우
        if (remoteMessage.getNotification() != null) {
            // 알림 내용을 받아서 사용자에게 알림을 표시
            RemoteMessage.Notification notification = remoteMessage.getNotification();
            sendNotification(notification.getTitle(), notification.getBody());
        }
    }
    
    private void sendNotification(String title, String body) {
    	Log.d("FCM TEST", "화면에 표시하기 직전");
    	Intent intent = new Intent(this, MainActivity.class);
		
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    	PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
    
    	String channelId = "Notification";
        Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder =
        	new NotificationCompat.Builder(this, channelId)
                       .setSmallIcon(R.drawable.ic_launcher_background)
                       .setContentTitle(title)
                       .setContentText(body)
                       .setAutoCancel(true)
                       .setSound(defaultSoundUri)
                       .setContentIntent(pendingIntent);
    
    	NotificationManager notificationManager =
                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    
    	// 안드로이드 O(API 26) 이상에서는 알림 채널이 필요합니다.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        	NotificationChannel channel = new NotificationChannel(channelId,
            	"Channel human readable title",
                 NotificationManager.IMPORTANCE_DEFAULT);
  			notificationManager.createNotificationChannel(channel);
    	}
    
    	notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    	Log.d("FCM TEST", "화면에 표시하기까지 성공");
    }
}

푸시 알림 처리 로직을 작성했으면 푸시 알림을 화면에 띄워주는 코드를 작성한다. 나는 테스트용 코드였기 때문에 간단하게 MainActivity에 코드를 작성하였다.

FirebaseMessaging.getInstance().getToken().addOnCompleteListener(new OnCompleteListener<String>() {
    @Override
    public void onComplete(@NonNull Task<String> task) {
    	if (!task.isSuccessful()) {
          Log.w(TAG, "Fetching FCM registration token failed", task.getException());
          return;
      	}

		// Get new FCM registration token
        String token = task.getResult();

		// Log and toast
        Log.d(TAG, "FCM Token: " + token);
        Toast.makeText(MainActivity.this, "FCM Token: " + token, Toast.LENGTH_SHORT).show();
    }
});

3. Spring Boot 코드 작성

마지막으로 서버단을 구현한다

FCM 서버에 접근하기 위한 비공개 키를 다운로드 받는다.

비공개 키 다운로드

새 비공개 키 생성을 눌러서 키를 받고, Spring Boot 프로젝트에 넣어준다. 나는 resource 폴더에 넣어주었다. 키 파일에는 공개되면 안되는 정보가 있기 때문에 깃이그노어 처리도 반드시 해주어야 한다!

키를 Spring Boot 프로젝트에 넣었다면, 환경 설정을 위한 FirebaseConfig 파일을 만들어준다. 여기에서 키 파일을 불러온다.

@Configuration
public class FirebaseConfig {
	@PostConstruct
    public void init(){
    	try{
        	InputStream serviceAccount = new ClassPathResource("worryboxFirebaseKey.json").getInputStream();
        	FirebaseOptions options = new FirebaseOptions.Builder()
            		.setCredentials(GoogleCredentials.fromStream(serviceAccount))
                    .build();
                        
         	if (FirebaseApp.getApps().isEmpty()) { // FirebaseApp이 이미 초기화되어 있지 않은 경우에만 초기화 실행
            	FirebaseApp.initializeApp(options);
            }
        } catch (Exception e){
        	e.printStackTrace();
        }
    }
}

환경 설정을 완료했으면 사용자 토큰을 받기 위한 API를 만들어준다. 다음은 토큰을 받기 위한 Request DTO이다. API 작성 코드는 프로젝트 상황에 맞게 변경해주면 된다.

@Getter
public class PostTokenReq {
	@NotBlank(message="토큰을 입력해야 합니다.")
    @Schema(description = "FCM Token", example = "")
    String token;
}

DTO 작성을 완료했으면, Controller와 Service를 구현해준다.

/* FCM Token 서버 저장 API */
@PostMapping("/{userId}/token")
public BaseResponse<String> getToken(@PathVariable Long userId, @Valid @RequestBody PostTokenReq postTokenReq) {
	try {
    	return new BaseResponse<>(FCMService.getToken(userId, postTokenReq.getToken()));
    } catch (BaseException e) {
    	return new BaseResponse<>(e.getStatus());
    }
}
@Transactional
public String getToken(Long userId, String token) throws BaseException {
	// 해당 아이디 가진 유저가 존재하는지 검사
    User userById = userRepository.findByIdAndStatus(userId, Status.A)
    	.orElseThrow(() -> new BaseException(BaseResponseStatus.BASE_INVALID_USER));
    
    userById.setFCMToken(token);
    return "토큰이 성공적으로 저장되었습니다";
}

토큰을 받는 API를 만들었다면, 본격적으로 FCM 서버에 푸시 알림을 요청하는 코드를 작성한다. 나의 경우는 푸시 알림 제목, 내용만 보내면 됐기 때문에 따로 Notification 클래스를 만들지는 않았다! 이렇게 하니 코드가 엄청 간단했다. 복잡한 데이터를 보내는 경우에는 파이어베이스 공식 문서의 데이터 구조를 참고해서 클라이언트와 의논하는 것이 좋을 것 같다.

public void sendMessage(String token, String title, String body) throws FirebaseMessagingException {
	String message = FirebaseMessaging.getInstance().send(Message.builder()
    	.setNotification(Notification.builder()
        .setTitle(title)
        .setBody(body)
        .build())
        .setToken(token)  // 대상 디바이스의 등록 토큰
        .build());
    
    System.out.println("Sent message: " + message);
}

4. 실행 결과

내가 보낸 푸시 알림이 앱에 성공적으로 도착했다. 예전부터 FCM 구현을 정말 해보고 싶었는데 생각보다 간단해서 놀랐다.
푸시 알림 보내기 성공!

참고 자료


Firebase 클라우드 메시징
[Spring Boot] FCM 구현하기 - 1
[Spring Boot] FCM Push 서버 구현하기 - 2
[React, PWA] 클라이언트에서 웹 푸시(fcm) 설정하기
[Spring Boot] FCM을 통해 Push 알림 보내기 1
[Spring Boot] FCM을 통해 Push 알림 보내기 1
FCM, Spring Boot를 사용하여 웹 푸시 기능 구현하기
FCM(Firebase - Cloud - Messaging)이란? + FCM Token, FCM Topic
FCM 서버 프로토콜을 이용한 Push 전송

profile
왔다 정보리

0개의 댓글