React Native Expo 환경에서 수익화를 위해 Google AdMob을 도입하고자 할 때, 코드를 작성하기 전 반드시 선행되어야 할 환경 세팅 과정을 정리한다.
UI 컴포넌트 배치나 백엔드 검증 로직을 제외하고, '이대로 세팅하지 않으면 빌드가 실패하거나 광고가 노출되지 않는' 필수 설정만을 담았다. 이 글의 순서대로 진행하면 문제없이 도입 준비를 마칠 수 있다.
AdMob 도입 전 필수 환경 세팅 가이드
Expo에서 AdMob을 사용하기 위해서는 react-native-google-mobile-ads 라이브러리가 필요하다. 하지만 단순히 패키지만 설치해서는 작동하지 않으며, Google Console 설정과 app.json 설정이 정확히 맞물려야 한다.
다음의 5단계를 순서대로 적용한다.
1. Google AdMob 콘솔: 앱 추가 및 ID 발급
가장 먼저 구글 애드몹 콘솔에서 앱을 등록하고 고유 ID를 발급받아야 한다.
- Google AdMob 접속 및 로그인한다.
- [앱] > [앱 추가] 버튼을 클릭한다.
- 플랫폼 선택: Android와 iOS를 각각 별도로 생성해야 한다. (앱이 출시 전이라면 마켓 등록 여부에 '아니요'를 선택한다).
- 앱 이름 입력: 식별하기 용이한 이름을 입력한다.
- 앱 ID (App ID) 확인:
- 생성 완료 후 표시되는
ca-app-pub-xxxxxxxxxxxxxxxx~xxxxxxxxxxxxxxxx형태의 ID를 복사해 둔다. - 주의: 광고 단위 ID(Banner ID 등)와 혼동해서는 안 된다. 설정 단계에서 필요한 것은 물결표(
~)가 포함된 앱 ID다.
- 생성 완료 후 표시되는
2. 라이브러리 설치
터미널을 열고 프로젝트 경로에서 아래 명령어를 입력한다. npx expo install을 사용하면 현재 Expo SDK 버전에 호환되는 최적의 버전을 자동으로 설치한다.
npx expo install react-native-google-mobile-ads expo-tracking-transparencyexpo-tracking-transparency가 필요한 이유
iOS 14.5 이상부터는 앱 추적 투명성(ATT) 승인을 받아야만 개인화된 광고 송출이 가능하다. 이를 누락할 경우 앱 심사에서 거절(Reject)되거나 광고 수익률(eCPM)이 현저히 낮아질 수 있으므로 반드시 함께 설치해야 한다.
3. app.json 설정 (핵심 단계)
가장 중요한 단계다. Expo Config Plugin을 통해 네이티브 설정 파일(AndroidManifest.xml, Info.plist)에 필요한 정보를 자동으로 주입한다.
프로젝트의 app.json 파일을 열어 plugins 배열에 아래 내용을 추가한다.
{
"expo": {
"name": "YourAppName",
// ... 기존 설정들 ...
"plugins": [
[
"react-native-google-mobile-ads",
{
"androidAppId": "ca-app-pub-xxxxxxxx~xxxxxxxx",
"iosAppId": "ca-app-pub-xxxxxxxx~xxxxxxxx",
"userTrackingPermission": "이 식별자는 귀하에게 맞춤형 광고를 제공하기 위해 사용됩니다."
}
],
[
"expo-tracking-transparency",
{
"userTrackingPermission": "이 식별자는 귀하에게 맞춤형 광고를 제공하기 위해 사용됩니다."
}
]
]
}
}- androidAppId: 1단계에서 발급받은 Android용 앱 ID를 입력한다.
- iosAppId: 1단계에서 발급받은 iOS용 앱 ID를 입력한다.
- userTrackingPermission: iOS 앱 실행 시 사용자에게 표시될 추적 허용 팝업의 문구다. 심사 통과를 위해 명확한 목적(예: 맞춤형 광고 제공)을 명시해야 한다.
4. iOS SKAdNetwork 설정
react-native-google-mobile-ads 플러그인을 app.json에 추가하면, 빌드 시 Google이 권장하는 SKAdNetwork ID들이 Info.plist에 자동으로 포함된다. 따라서 Google AdMob만 단독으로 사용한다면 별도의 수동 설정은 필요 없다.
단, IronSource, Unity Ads 등 다른 광고 네트워크와 미디에이션을 구성할 계획이라면 해당 네트워크들의 SKAdNetwork ID를 별도로 추가해야 한다.
5. Development Build (개발 클라이언트) 생성
AdMob 라이브러리는 네이티브 코드를 포함하므로, 기본적으로 제공되는 Expo Go 앱에서는 작동하지 않는다. 반드시 Development Build(개발용 빌드) 를 생성하여 테스트해야 한다.
-
EAS CLI 설치 (미설치 시):
npm install -g eas-cli eas login -
eas.json 설정:
eas.json파일이 없다면eas build:configure로 생성하고,development프로필이 존재하는지 확인한다.{ "build": { "development": { "developmentClient": true, "distribution": "internal" }, // ... 기타 프로필 } } -
빌드 실행:
시뮬레이터 또는 실제 기기 테스트용 빌드를 실행한다.# iOS 시뮬레이터용 eas build --profile development --platform ios # Android APK (기기 설치용) eas build --profile development --platform android -
설치 및 실행:
빌드가 완료되면 제공되는 QR코드나 링크를 통해 기기에 앱을 설치한다. 이후에는 Expo Go 아이콘이 아닌, 해당 앱 아이콘을 통해 개발 서버에 접속하여 개발을 진행한다.
6. 보상형 광고(SSV) 구현 시 필수 개념
단순 배너 광고가 아닌, 사용자에게 보상을 지급하는 보상형 광고를 구현할 계획이라면 아래의 SSV(Server-Side Verification) 개념을 미리 숙지해야 삽질을 피할 수 있다.
-
userId 주입 시점:
react-native-google-mobile-ads라이브러리 스펙상 userId는 광고를 생성하고 로드하는 시점에 serverSideVerificationOptions로 주입해야 한다. 따라서 React Hook 작성 시 userId가 변경될 때마다 광고 객체를 재생성하는 로직이 필요하다. -
보상 지급의 주체: 클라이언트 앱에서 발생하는
EARNED_REWARD이벤트만 믿고 보상을 지급하면 보안에 취약하다(해킹 위험). 올바른 흐름: 앱(광고 시청) -> AdMob 서버 -> 내 백엔드 서버로 콜백(Webhook) -> 백엔드에서 보상 지급
만약 내가 해커라면, 클라이언트에서 보상을 지급하는 구조라면 다음과 같은 시나리오로 조작했을 것이다.
먼저 jadx 같은 디컴파일 툴로 앱을 분석한다. 코드를 살펴보면 단순한 로직이 보인다. onUserEarnedReward가 호출되면 즉시 보상을 지급하고, 트랜잭션 ID 검증도 없다. 취약 지점이다.
다음 단계는 Frida 후킹이다. Frida 스크립트를 짜서 앱 프로세스에 주입하고, 런타임 메모리를 직접 조작한다. 내가 노리는 건 광고 SDK가 "보상 지급"을 알리는 자바 메서드 하나다.
// 가상의 공격 스크립트
Java.perform(function() {
// 구글 AdMob의 보상 리스너를 찾는다
// 광고를 보여주는 함수를 가로챈다
// 0.1초 만에 '광고 시청 완료' 이벤트를 강제로 발생시킨다.
// 광고 보상 지급 함수 실행시킨다.
// 실제 광고 화면은 보여주지도 않고 종료
});이제 [광고 보고 보상 받기] 버튼을 광클해서 무한 파밍 한다. 일반 사용자라면 30초를 기다려야 10포인트를 받겠지만, 나는 버튼을 누르는 순간 광고 화면은 뜨지도 않았는데 "지급 완료" 토스트 메시지가 뜬다.
- transaction_id의 역할: AdMob은 콜백을 보낼 때 고유한
transaction_id를 자동으로 생성해서 보내준다. 백엔드에서는 이 ID를 저장하여 중복 지급 방지 처리를 해야 한다. 개발자가 임의로 생성하는 것이 아니다.
요약 체크리스트
- AdMob 콘솔: iOS/Android 앱을 각각 생성하고 App ID(
~) 를 확보했는가? - 패키지:
react-native-google-mobile-ads,expo-tracking-transparency를 설치했는가? - app.json:
plugins섹션에 App ID와 권한 문구를 정확히 기입했는가? - 빌드: Expo Go가 아닌 Development Build를 새로 생성하여 실행했는가?
- (보상형 광고):
userId를 광고 로드 시점에 주입하고, 보상 지급은 백엔드 콜백(SSV)으로 처리하도록 설계했는가?
