본문 바로가기
조그만 기술로 세상을 이롭게/나여기있어

FCM PUSH 알림이란? 앱에서 상태와 위치를 실시간으로 보내는 기술 구조 정리

by eplus 2026. 5. 29.

1. PUSH 알림이 필요한 이유

모바일 앱을 만들다 보면 사용자가 앱을 계속 열어두지 않아도 중요한 정보를 즉시 알려야 하는 경우가 많다.

예를 들면 다음과 같은 상황이다.

가족 안심 앱에서 “위급” 상태 전송
버스 도착 알림
날씨 특보 알림
주문/배송 상태 알림
서버 장애 알림
관리자 공지 알림

이때 사용하는 대표적인 기술이 PUSH 알림이다.

PUSH 알림은 앱이 서버에 계속 접속해 있는 방식이 아니라, 서버가 필요할 때 모바일 기기로 알림을 보내는 방식이다.
Android 앱에서는 일반적으로 Firebase Cloud Messaging, 줄여서 FCM을 많이 사용한다.


2. FCM이란?

FCM은 Google Firebase에서 제공하는 메시징 서비스다.
앱 서버가 FCM에 메시지를 보내면, FCM이 Android, iOS, Web 앱으로 메시지를 전달해준다.

구조는 다음과 같다.

앱 서버
 ↓
Firebase Cloud Messaging
 ↓
사용자 스마트폰 앱

Firebase 공식 문서에서도 FCM은 앱 서버 또는 신뢰할 수 있는 서버 환경에서 메시지 요청을 보내고, FCM 백엔드가 이를 사용자 기기의 클라이언트 앱으로 라우팅하는 구조라고 설명한다.

즉, 앱이 직접 다른 앱으로 PUSH를 보내는 것이 아니다.
중간에 반드시 FCM 서버가 있고, 보통은 자체 서버가 FCM에 발송 요청을 한다.


3. FCM PUSH의 기본 구성 요소

FCM PUSH를 이해하려면 다음 4가지를 알아야 한다.

1. Firebase 프로젝트
2. Android 앱 설정 파일
3. FCM 등록 토큰
4. 서버용 서비스 계정 JSON

1) Firebase 프로젝트

Firebase Console에서 프로젝트를 만들고 Android 앱을 등록한다.

예:

Firebase 프로젝트명: safecheck
Android 패키지명: com.eplus.safecheck

여기서 Android 패키지명은 실제 앱의 패키지명과 같아야 한다.


2) google-services.json

Android 앱을 Firebase 프로젝트와 연결하기 위한 앱 설정 파일이다.

위치 예:

Platforms/Android/google-services.json

이 파일은 앱용 설정 파일이다.
서버에서 FCM을 발송할 때 쓰는 파일이 아니다.


3) FCM 등록 토큰

앱이 설치되고 실행되면 Firebase SDK가 해당 앱 인스턴스에 대한 등록 토큰을 생성한다.
Firebase 문서에서도 앱 초기 실행 시 FCM SDK가 클라이언트 앱 인스턴스의 등록 토큰을 생성하며, 특정 앱 인스턴스를 대상으로 메시지를 보내려면 이 토큰을 사용한다고 설명한다.

예:

dO0uukqWTZ2VppYCG5ullN:APA91bF...

이 토큰은 서버 DB에 저장해야 한다.

device_id
nickname
fcm_token
last_seen_at

서버는 나중에 이 토큰을 대상으로 PUSH를 보낸다.


4) Firebase 서비스 계정 JSON

서버에서 FCM을 발송하기 위해 필요한 관리자 권한 파일이다.

Firebase Admin SDK를 사용하려면 Firebase 프로젝트, 서비스 계정, 서비스 계정 자격 증명 파일이 필요하다.

파일 예:

firebase-service-account.json

서버 저장 위치 예:

C:\secure\firebase-service-account.json

주의할 점은 이 파일을 절대 앱 내부나 웹 공개 폴더에 두면 안 된다는 것이다.

나쁜 예:
C:\inetpub\wwwroot\
static\
GitHub 업로드
앱 프로젝트 내부 포함

좋은 예:
C:\secure\
환경변수 경로 지정
IIS App Pool 읽기 권한만 부여

4. FCM PUSH 전체 흐름

안심확인 앱을 예로 들면 전체 흐름은 다음과 같다.

1. 앱 실행
2. Firebase에서 FCM 토큰 발급
3. 앱이 서버에 deviceId와 fcmToken 등록
4. QR 코드로 보호자 폰과 연결
5. 사용자가 “좋음/보통/힘듦/위급” 상태 선택
6. 앱이 GPS 위치와 상태를 서버로 전송
7. 서버가 MariaDB에 상태 로그 저장
8. 서버가 연결된 상대 폰의 FCM 토큰 조회
9. 서버가 FCM으로 PUSH 발송
10. 상대 폰에서 알림 수신
11. 앱 화면에 상태, 위치, 전송일시 표시

그림으로 보면 다음과 같다.

보호 대상 폰
  ↓ 상태 + GPS 전송

Flask 서버
  ↓ DB 저장
  ↓ 연결된 상대 FCM 토큰 조회

Firebase Cloud Messaging
  ↓ PUSH 전달

보호자 폰
  ↓ 알림 수신
  ↓ 앱 화면/지도 표시

5. 앱이 직접 PUSH를 보내면 안 되는 이유

처음 생각할 때는 “폰 A가 폰 B의 FCM 토큰을 알고 있으니 직접 FCM으로 보내면 되지 않을까?”라고 생각할 수 있다.

하지만 이렇게 하면 안 된다.

FCM 발송에는 서버 권한이 필요하다.
Firebase Admin SDK나 HTTP v1 API를 사용하려면 서비스 계정 자격 증명이 필요한데, 이 키를 앱에 넣으면 APK 분석으로 노출될 수 있다.

위험한 구조:

앱 내부에 Firebase 서비스 계정 키 포함
↓
APK 분석으로 키 노출
↓
누구나 임의 PUSH 발송 가능

안전한 구조:

앱
↓
내 서버
↓
Firebase Admin SDK
↓
FCM PUSH

따라서 PUSH 발송은 반드시 신뢰 가능한 서버 환경에서 처리해야 한다. Firebase 공식 문서도 앱 서버 또는 신뢰할 수 있는 서버 환경에서 FCM 백엔드로 메시지를 보내는 구조를 안내한다.


6. Notification Message와 Data Message

FCM 메시지는 크게 두 종류가 있다.

1. Notification Message
2. Data Message

Firebase 문서에 따르면 Notification Message는 FCM SDK가 자동으로 표시하는 알림 메시지이고, Data Message는 앱이 직접 처리하는 메시지다.


1) Notification Message

일반적인 알림 표시용이다.

예:

{
  "title": "[안심확인] 내 폰",
  "body": "상태: 좋음 / 시간: 2026-05-28 07:16"
}

장점:

앱이 꺼져 있어도 알림 표시가 쉽다
구현이 비교적 단순하다
Android 시스템 알림에 자동 표시된다

단점:

앱 내부 화면 갱신은 별도 처리 필요
알림 클릭 후 처리 흐름을 별도로 구현해야 한다

2) Data Message

앱이 직접 처리할 데이터를 담는 메시지다.

예:

{
  "type": "safecheck_status",
  "senderDeviceId": "1fed7f587ec649678b0cfee0a772d344",
  "senderName": "아버지폰",
  "statusCode": "GOOD",
  "statusText": "좋음",
  "latitude": "35.178445",
  "longitude": "128.59846333333334",
  "sentAt": "2026-05-28T06:54:11"
}

장점:

앱 내부 로그 저장 가능
화면 자동 갱신 가능
지도 위치 표시 가능
상태별 분기 처리 가능

단점:

앱에서 직접 수신 처리 코드를 작성해야 한다
백그라운드/종료 상태 처리를 별도로 고려해야 한다

안심확인 앱처럼 상태, 위치, 전송일시를 앱 내부에서 처리해야 하는 경우에는 notification + data를 함께 쓰거나, 상황에 따라 data-only 방식도 검토할 수 있다.


7. 서버에서 FCM을 보내는 방식

Python Flask 서버에서 FCM을 보내려면 firebase-admin 패키지를 사용한다.

설치:

pip install firebase-admin

초기화 예:

import firebase_admin
from firebase_admin import credentials, messaging

cred = credentials.Certificate(r"C:\secure\firebase-service-account.json")
firebase_admin.initialize_app(cred)

단일 기기로 PUSH 발송:

def send_fcm_to_token(token, title, body, data):
    message = messaging.Message(
        token=token,
        notification=messaging.Notification(
            title=title,
            body=body
        ),
        data=data
    )

    response = messaging.send(message)
    return response

Firebase Admin SDK는 특정 기기, 여러 기기, 메시지 목록 등에 메시지를 보내는 기능을 제공한다. 특정 기기로 보낼 때는 등록 토큰을 대상으로 메시지를 전송한다.


8. 상태 전송 API에서 PUSH 발송하기

안심확인 앱에서는 /status/send API에서 상태를 수신한 즉시 PUSH를 발송한다.

흐름:

POST /api/safecheck/status/send
↓
상태 로그 저장
↓
연결된 상대 폰 조회
↓
상대 fcm_token 조회
↓
FCM 발송
↓
발송 로그 저장

예시 코드 구조:

@safecheck_bp.route("/status/send", methods=["POST"])
def send_status():
    data = request.get_json(force=True)

    device_id = data.get("deviceId")
    status_text = data.get("statusText")
    latitude = data.get("latitude")
    longitude = data.get("longitude")
    sent_at = data.get("sentAt")

    # 1. 상태 로그 저장
    status_log_id = insert_status_log(
        device_id,
        status_text,
        latitude,
        longitude,
        sent_at
    )

    # 2. 연결된 상대 폰 조회
    receivers = find_pair_receivers(device_id)

    # 3. FCM 발송
    for receiver in receivers:
        send_fcm_to_token(
            receiver["fcm_token"],
            "[안심확인]",
            f"상태: {status_text} / 시간: {sent_at}",
            {
                "type": "safecheck_status",
                "statusLogId": str(status_log_id),
                "statusText": status_text,
                "latitude": str(latitude),
                "longitude": str(longitude),
                "sentAt": sent_at
            }
        )

    return {
        "result": "ok",
        "statusLogId": status_log_id,
        "receiverCount": len(receivers)
    }

9. 발송 결과 로그가 중요한 이유

PUSH는 “보냈다”와 “사용자가 봤다”가 다르다.
서버 입장에서는 FCM에 정상 요청했는지, 실패했는지 기록해야 한다.

그래서 sc_fcm_send_log 같은 테이블이 필요하다.

예:

status_log_id
from_device_id
to_device_id
title
body
success_yn
error_message
created_at

성공:

success_yn = Y
error_message = NULL

실패:

success_yn = N
error_message = Firebase service account file not found

운영 중 실제로 가장 많이 만나는 문제는 다음과 같다.

서비스 계정 JSON 경로 오류
FCM 토큰 만료
잘못된 토큰
알림 권한 미허용
앱 삭제 후 남은 토큰
네트워크 장애
서버 권한 문제

따라서 발송 로그는 단순 기록이 아니라 운영 장애를 찾는 핵심 자료다.


10. FCM 토큰 관리

FCM 토큰은 영구 고정값이라고 보면 안 된다.
앱 재설치, 앱 데이터 삭제, 기기 변경, Firebase 내부 갱신 등에 따라 토큰이 바뀔 수 있다.

Firebase의 토큰 관리 가이드도 비활성 기기나 오래된 등록 토큰으로 인해 발송 리소스가 낭비되고, 전달률 지표가 왜곡될 수 있으므로 토큰 관리가 중요하다고 설명한다.

운영 DB에서는 다음 정보를 관리하는 것이 좋다.

device_id
fcm_token
last_seen_at
app_version
platform
active_yn

앱 실행 시마다 서버에 현재 토큰을 다시 등록하면 토큰 변경 문제를 줄일 수 있다.

앱 실행
↓
현재 FCM 토큰 확인
↓
서버에 device/register 호출
↓
DB 토큰 갱신

11. Android 13 이상 알림 권한

Android 13 이상에서는 알림 권한이 중요하다.

앱에서 FCM PUSH는 도착했지만 알림이 화면에 보이지 않는 경우가 있다.
이때는 Android 알림 권한이 꺼져 있을 수 있다.

확인 위치:

설정
→ 앱
→ 안심확인
→ 알림
→ 알림 허용

앱에서도 POST_NOTIFICATIONS 권한을 선언하고 요청해야 한다.

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

12. 앱 실행 상태별 PUSH 처리

PUSH 처리는 앱 상태에 따라 다르게 느껴진다.

앱이 실행 중인 경우

FCM data 수신
↓
앱 내부 이벤트 발생
↓
수신 로그 저장
↓
화면 자동 갱신
↓
지도 위치 표시

이 경우 사용자는 앱 화면에서 바로 상태를 볼 수 있어야 한다.


앱이 백그라운드인 경우

FCM 수신
↓
Android 시스템 알림 표시
↓
사용자가 알림 클릭
↓
앱 실행
↓
서버에서 최근 로그 조회
↓
화면 표시

앱이 종료된 경우

앱이 완전히 종료된 경우에는 수신 처리 방식이 더 까다롭다.
그래서 중요한 데이터는 PUSH에만 의존하지 말고 반드시 서버 DB에 저장해야 한다.

안전한 구조:

PUSH = 알림 역할
DB 로그 = 실제 이력 기준

앱이 다시 실행되면 서버에서 최근 30건을 조회해 화면을 복원한다.


13. 관리자 프로그램에서 재전송이 필요한 이유

PUSH는 네트워크, 토큰, 권한 문제로 실패할 수 있다.
따라서 관리자 프로그램에서 실패 건을 확인하고 재전송할 수 있어야 한다.

관리 프로그램 기능 예:

FCM 발송 로그 조회
성공/실패 여부 확인
실패 사유 확인
특정 기기 수동 PUSH
실패 건 재전송
기기 비활성 처리

수동 PUSH도 관리 프로그램이 Firebase에 직접 보내는 것이 아니라 서버 API를 호출하는 방식이 안전하다.

C# 관리 프로그램
↓
Flask 관리자 API
↓
Firebase Admin SDK
↓
FCM PUSH

이렇게 하면 Firebase 서비스 계정 키를 관리자 PC에 배포하지 않아도 된다.


14. FCM 운영 시 주의할 점

1) 서비스 계정 키 보호

서비스 계정 JSON은 Firebase 관리자 권한을 가진 민감 파일이다.

절대 앱에 포함 금지
절대 GitHub 업로드 금지
절대 웹 공개 폴더 저장 금지

2) FCM 토큰 최신화

앱 실행 시마다 토큰을 서버에 갱신하는 것이 좋다.

앱 실행
토큰 확인
서버 저장
last_seen_at 갱신

3) 실패 로그 관리

실패 로그를 남기지 않으면 PUSH 장애 원인을 찾기 어렵다.

success_yn
error_message
created_at
to_device_id

이 네 가지는 반드시 관리하는 것이 좋다.


4) PUSH에 민감정보를 너무 많이 넣지 않기

PUSH payload에 개인정보를 과도하게 넣는 것은 좋지 않다.

예를 들어 정확한 위치, 이름, 전화번호, 상세 메시지 등은 신중하게 다뤄야 한다.
보안 연구에서도 일부 앱들이 푸시 알림 payload를 통해 사용자 식별자, 발신자/수신자 이름, 전화번호, 메시지 내용 같은 민감 정보를 노출한 사례가 지적된 바 있다.

권장 구조:

PUSH에는 최소 정보만 포함
상세 정보는 앱이 서버 API로 조회

안심확인 앱처럼 위치를 표시해야 하는 경우에도, 서비스 목적과 동의 절차가 분명해야 한다.


15. 안심확인 앱에서의 FCM 적용 구조

안심확인 앱의 FCM 구조는 다음과 같이 정리할 수 있다.

MAUI Android 앱
- FCM 토큰 발급
- 서버에 토큰 등록
- PUSH 수신 처리
- 최근 로그 저장
- 지도 표시

Flask 서버
- 기기 등록
- QR 연결 관리
- 상태 로그 저장
- FCM 즉시 발송
- 발송 로그 저장

MariaDB
- sc_device
- sc_pair
- sc_status_log
- sc_fcm_send_log

C# 관리자 프로그램
- 기기 조회
- 상태 로그 조회
- 발송 로그 조회
- 실패 PUSH 재전송
- 수동 PUSH 발송

역할을 나누면 다음과 같다.

앱 = 입력과 수신
서버 = 판단과 발송
DB = 이력과 연결
관리 프로그램 = 운영과 재처리

16. 마무리

FCM PUSH는 단순히 알림을 띄우는 기능처럼 보이지만, 실제 운영에서는 다음 요소를 함께 설계해야 한다.

FCM 토큰 발급
토큰 DB 저장
토큰 갱신
서버 발송 구조
서비스 계정 키 보안
알림 권한
발송 로그
실패 재전송
앱 상태별 수신 처리
개인정보 보호

특히 가족 안심, 노인 돌봄, 아이 위치 확인, 위급 알림 같은 앱에서는 PUSH가 단순 편의 기능이 아니라 핵심 안전 기능이 된다.

따라서 가장 안정적인 구조는 다음과 같다.

사용자 앱
↓
내 서버
↓
MariaDB 로그 저장
↓
Firebase Cloud Messaging
↓
상대방 앱
↓
화면/지도 표시

FCM은 무료에 가깝게 시작할 수 있고 Android 앱과의 연동도 좋지만, 운영 품질을 높이려면 반드시 서버 로그, 실패 관리, 토큰 관리, 보안 설계를 함께 해야 한다.

반응형