프로그래밍Web Hosting

PHP에서 스마트폰으로 진짜 푸시 알림 쏘기 – 2부

구글 파이어베이스는 보안이 매우 강력해서, 예전처럼 단순한 비밀번호 하나로는 알림을 보낼 수 없고 ‘서비스 계정 키(비밀키 파일)’를 이용해 매번 임시 출입증(Access Token)을 발급받아 보내야 합니다. (FCM HTTP v1 API 방식)

복잡한 라이브러리 설치(Composer 등) 없이 순수 PHP 코드만으로 완벽하게 작동하도록 아래 3단계를 그대로 따라와 주세요!

1단계: 백엔드용 ‘비밀키 파일’ 다운로드

프론트엔드용 공개키와 달리, 이건 우리 PHP 서버만 알고 있어야 하는 진짜 마스터키입니다.

  1. 파이어베이스 콘솔에 접속합니다.
  2. 왼쪽 메뉴 상단 톱니바퀴(프로젝트 설정) ➡️ [서비스 계정] 탭으로 들어갑니다.
  3. 하단의 [새 비공개 키 생성] 버튼을 누릅니다.
  4. .json 확장자로 된 파일이 다운로드됩니다.
  5. 이 파일의 이름을 알아보기 쉽게 **firebase-adminsdk.json**으로 변경한 뒤, 에버헬스 서버의 최상위 폴더(index.php가 있는 곳)에 업로드해 주세요.


2단계: send_push.php 생성 (알림 발송 파일)

서버에 저장된 토큰을 불러와 파이어베이스로 알림 발송 명령을 내리는 파일입니다. 최상위 폴더에 send_push.php를 만들고 아래 코드를 넣어주세요.

<?php
/*
    send_push.php - 파이어베이스 FCM 알림 발송 테스트
*/
ini_set('display_errors', 1);
error_reporting(E_ALL);
include 'db_conn.php';

// 1. 방금 업로드한 파이어베이스 마스터 비밀키 파일 경로
$service_account_file = 'firebase-adminsdk.json';

if (!file_exists($service_account_file)) {
    die("오류: firebase-adminsdk.json 파일을 찾을 수 없습니다.");
}

// 2. 구글 OAuth 2.0 Access Token 자동 발급 함수 (핵심 보안 로직)
function get_fcm_access_token($key_file) {
    $key = json_decode(file_get_contents($key_file), true);
    
    $header = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])));
    $now = time();
    $payload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(json_encode([
        'iss' => $key['client_email'],
        'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
        'aud' => $key['token_uri'],
        'exp' => $now + 3600,
        'iat' => $now
    ])));
    
    // RSA 서명 생성
    openssl_sign($header . "." . $payload, $signature, $key['private_key'], "sha256WithRSAEncryption");
    $jwt = $header . "." . $payload . "." . str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

    // 구글 서버에 토큰 요청
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $key['token_uri']);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=" . $jwt);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $res = curl_exec($ch);
    curl_close($ch);
    
    return json_decode($res, true)['access_token'];
}

// 토큰 및 프로젝트 ID 추출
$access_token = get_fcm_access_token($service_account_file);
$project_id = json_decode(file_get_contents($service_account_file), true)['project_id'];

// 3. DB에서 가장 최근에 가입한(방금 유저님이 테스트한) 토큰 1개만 가져오기
$sql = "SELECT fcm_token FROM eh_fcm_tokens ORDER BY id DESC LIMIT 1";
$result = $conn->query($sql);

if ($result->num_rows > 0) {
    $row = $result->fetch_assoc();
    $target_token = $row['fcm_token'];

    // 4. 스마트폰으로 보낼 알림 내용 구성
    $message = [
        "message" => [
            "token" => $target_token,
            "notification" => [
                "title" => "💧 수분 체크",
                "body" => "오늘 물 8잔 마셨나요? 다이어리에 기록하고 목표를 달성하세요!"
            ],
            // 알림을 클릭했을 때 이동할 URL 데이터
            "data" => [
                "url" => "https://도메인주소.co.kr/daily_diary.php"
            ]
        ]
    ];

    // 5. 파이어베이스 서버로 알림 발송 명령 (cURL)
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://fcm.googleapis.com/v1/projects/" . $project_id . "/messages:send");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $access_token,
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = curl_exec($ch);
    curl_close($ch);

    echo "<h3>푸시 발송 완료!</h3>";
    echo "<p>파이어베이스 응답 결과: " . htmlspecialchars($response) . "</p>";
} else {
    echo "DB에 등록된 푸시 토큰이 없습니다.";
}
?>


3단계: 대망의 첫 푸시 발송 테스트!

  1. 스마트폰 화면을 끄거나, 에버헬스가 아닌 다른 앱(카카오톡이나 바탕화면 등)을 띄워놓고 대기합니다. (브라우저가 켜져 있으면 조용히 처리될 수 있습니다.)
  2. PC 브라우저에서 방금 만든 주소로 접속합니다. 👉 https://도메인주소.co.kr/send_push.php
  3. 접속하는 순간 스마트폰이 “띠링!” 하고 울리며 잠금화면에 “💧 수분 체크: 오늘 물 8잔 마셨나요?” 라는 알림이 예쁘게 뜰 것입니다!
  4. 그 알림을 터치하면 우리가 지정한 daily_diary.php 다이어리 화면으로 스르륵 접속됩니다.

이 기술이 바로 대형 서비스들이 유저를 다시 불러모으는 가장 강력한 마케팅 도구입니다. 스마트폰에 성공적으로 띠링~ 하고 알림이 울렸는지 꼭 확인해 보시고 소감 알려주세요! 🤩


아주 중요한 보안적인 요소

firebase-adminsdk.json 파일은 절대 노출되면 안됩니다. firebase-adminsdk.json 파일이 index.php와 같은 최상위 폴더(웹 루트)에 그대로 있다면, 누군가 주소창에 https://도메인주소.co.kr/firebase-adminsdk.json이라고 치는 순간 파이어베이스 마스터키가 통째로 다운로드됩니다. 해커가 이 키를 가지면 에버헬스 이름으로 스팸 푸시를 무한대로 보내거나 파이어베이스 프로젝트를 망가뜨릴 수 있는 초대형 보안 사고가 발생합니다.

웹 루트(public_html) 바깥으로 파일 이동 (가장 정석적이고 안전함)

서버의 폴더 구조를 마음대로 제어할 수 있다면, 애초에 외부에서 URL로 절대 접근할 수 없는 웹 루트 바깥으로 파일을 빼버리는 것이 정석입니다.

  1. 보통 웹사이트 파일들은 public_html 또는 www 라는 폴더 안에 들어있습니다.
  2. firebase-adminsdk.json 파일을 그 폴더보다 한 단계 위로 이동시킵니다.
    • 예: /home/user/public_html/ 에 웹사이트가 있다면, /home/user/firebase-adminsdk.json 위치로 옮깁니다.
  3. send_push.php 파일에서 키 파일을 불러오는 경로를 아래처럼 수정해 줍니다.

// '../' 를 사용해서 현재 폴더보다 한 단계 위로 올라가서 파일을 찾습니다.
$service_account_file = '../firebase-adminsdk.json'; 

LAMP 사용자라면, 비트나미 사용자이니, htdocs 전 폴더에 파일을 옮깁니다.

/opt/bitnami/apache/htdocs

콘솔에 접근 후 아래 명령어를 순자적으로 실행하세요.

cd /opt/bitnami/apache/htdocs
sudo mv firebase-adminsdk.json  ../

만약 푸시 알림이 안온다면?

푸시 발송 완료!

파이어베이스 응답 결과: { "name": "projects/test-1d1a2/messages/xxxxxxx" }

파이어베이스 응답 결과에 projects/.../messages/... 형태의 고유 ID가 떨어졌다는 것은, PHP 서버에서 구글 파이어베이스로 알림이 완벽하게 전달되었다는 뜻입니다! 백엔드 개발은 100% 성공입니다.

그런데 스마트폰(또는 PC)에 알림이 울리지 않았다면, 십중팔구 ‘파이어베이스의 포그라운드(Foreground) 함정’에 빠지셨거나 기기 자체의 알림 차단 설정 때문입니다.


원인 1: 웹사이트 화면을 띄워놓고 계셨나요? (가장 유력)

파이어베이스 웹 푸시의 가장 큰 특징은 “유저가 현재 그 사이트 화면을 보고 있을 때(포그라운드)는 기본적으로 알림창을 띄우지 않는다”는 것입니다. 우리가 작성한 firebase-messaging-sw.js 안의 코드가 onBackgroundMessage(백그라운드 메시지)이기 때문입니다.

✅ 확실한 테스트 방법:

send_push.php 파일에 접속하자마자 1초 만에 스마트폰 홈 화면으로 나가거나(앱 최소화), PC 브라우저 창을 최소화해 보세요! 혼자서 테스트하기 힘들다면 send_push.php 코드의 맨 위쪽(include 'db_conn.php'; 바로 아래)에 시간을 지연시키는 코드를 넣어보세요.

PHP

// 5초 대기 (이 시간 동안 빨리 창을 내리거나 다른 탭으로 이동하세요!)
sleep(5); 

이렇게 하고 접속한 뒤 바로 창을 내리고 기다렸을 때 알림이 온다면 정상입니다!


원인 2: 기존 서비스 워커가 꼬여있을 때

1부에서 만들었던 service-worker.js와 방금 만든 firebase-messaging-sw.js가 충돌하고 있을 수 있습니다.

✅ 해결 방법 (PC 크롬 기준):

  1. 웹사이트에서 F12(개발자 도구)를 엽니다.
  2. [Application] 탭 -> 좌측 [Service workers] 메뉴를 클릭합니다.
  3. 우측 화면에 등록된 서비스 워커가 보인다면 [Unregister] 버튼을 눌러 모조리 삭제합니다.
  4. 새로고침 후 [웹 푸시 알림 켜기 (FCM)] 버튼을 다시 눌러 새 토큰을 발급받고 테스트해 보세요.


원인 3: 기기(OS) 자체의 알림 차단 (방해 금지 모드)

크롬 브라우저에서는 알림을 허용했어도, 윈도우(PC)나 스마트폰(OS) 자체가 브라우저의 알림을 막고 있는 경우가 굉장히 많습니다.

  • PC (Windows): 우측 하단 알림 센터에 달 모양 아이콘(집중 지원 / 방해 금지 모드)이 켜져 있는지 확인하세요. 이게 켜져 있으면 알림이 화면에 안 뜨고 알림 센터에 조용히 쌓입니다. 윈도우 설정 > 시스템 > 알림에서 ‘Chrome’ 알림이 켜져 있는지도 확인해 주세요.
  • 스마트폰 (Android): 설정 > 애플리케이션 > Chrome > 알림 설정에서 알림이 허용되어 있는지 확인해 주세요.



보너스: 사이트를 보고 있을 때도 알림을 띄우고 싶다면?

유저가 에버헬스 사이트를 이용 중일 때도(포그라운드) 화면에 알림을 띄우고 싶다면, 프론트엔드(profile.php 등에 넣었던 스크립트) 쪽에 메시지 수신 로직을 하나 추가해야 합니다.

이전에 추가하셨던 파이어베이스 스크립트 하단에 아래 코드를 추가해 보세요.

JavaScript

// 앱을 켜놓고 있을 때(포그라운드) 알림이 오면 가로채서 화면에 띄워주기
messaging.onMessage((payload) => {
  console.log('포그라운드 메시지 수신:', payload);
  
  // SweetAlert2 등 예쁜 팝업으로 유저에게 알려주기
  Swal.fire({
      icon: 'info',
      title: payload.notification.title,
      text: payload.notification.body,
      confirmButtonText: '확인',
      confirmButtonColor: '#1abc9c'
  });
});

가장 유력한 용의자는 ‘원인 1번(화면을 띄워놓고 있어서)’입니다! sleep(5); 신공을 써보시고 화면을 끈 상태에서 스마트폰에 알림이 오는지 꼭 확인해 보세요! 성공을 응원합니다!

[연관자료]

Hi, I’m 관리자