서버 측 인증 콜백은 Google에서 외부 시스템에 보내고 Google에서 확장하는 쿼리 매개변수가 포함된 URL 요청입니다. 이를 통해 보상형 광고 또는 보상형 전면 광고와의 상호작용에 대해 사용자에게 보상을 제공해야 함을 알립니다. 보상형 SSV (서버 측 인증) 콜백은 클라이언트 측 콜백 스푸핑에 대한 추가 보호 계층을 제공하여 사용자에게 보상합니다.
이 가이드에서는 Tink Java 앱 서드 파티 암호화 라이브러리를 사용하여 보상형 SSV 콜백을 확인하여 콜백의 쿼리 매개변수가 올바른 값인지 확인하는 방법을 설명합니다. 이 가이드에서는 Tink를 사용하지만 ECDSA를 지원하는 서드 파티 라이브러리를 사용할 수 있습니다. AdMob UI에서 테스트 도구를 사용하여 서버를 테스트할 수도 있습니다.
Java spring-boot를 사용하는 리워드 SSV 예를 확인해 보세요.
기본 요건
- 광고 단위에서 보상형 서버 측 확인을 사용 설정하세요.
Tink Java 앱 라이브러리에서 RewardedAdsVerifier 사용
Tink Java 앱 GitHub 저장소에는 보상형 SSV 콜백을 확인하는 데 필요한 코드를 줄여주는 RewardedAdsVerifier
도우미 클래스가 포함되어 있습니다.
이 클래스를 사용하면 다음 코드로 콜백 URL을 확인할 수 있습니다.
RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
.fetchVerifyingPublicKeysWith(
RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
.build();
String rewardUrl = ...;
verifier.verify(rewardUrl);
예외를 발생시키지 않고 verify()
메서드를 실행하여 콜백 URL을 확인했습니다. 사용자에 대한 보상 섹션에는 사용자가 보상을 받는 경우에 대한 권장사항이 자세히 나와 있습니다. 보상형 SSV 콜백을 인증하기 위해 이 클래스에서 수행한 단계를 자세히 알아보려면 보상형 SSV 수동 인증 섹션을 읽어보세요.
SSV 콜백 매개변수
서버 측 인증 콜백에는 보상형 광고 상호작용을 설명하는 쿼리 매개변수가 포함되어 있습니다. 매개변수 이름, 설명 및 예시 값은 다음과 같습니다. 매개변수는 알파벳순으로 전송됩니다.
매개변수 이름 | 설명 | 예시 값 |
---|---|---|
ad_network | 이 광고를 처리한 광고 소스의 광고 소스 식별자입니다. ID 값에 해당하는 광고 소스 이름이 광고 소스 식별자 섹션에 표시됩니다. | 1953547073528090325 |
ad_unit | 보상형 광고를 요청할 때 사용된 AdMob 광고 단위 ID입니다. | 2747237135 |
custom_data |
setCustomData 에서 제공한 맞춤 데이터 문자열입니다.
앱에서 맞춤 데이터 문자열을 제공하지 않으면 이 쿼리 매개변수 값이 SSV 콜백에 표시되지 않습니다. |
SAMPLE_CUSTOM_DATA_STRING |
key_id | SSV 콜백을 인증할 때 사용하는 키입니다. 이 값은 AdMob 키 서버에서 제공하는 공개 키에 매핑됩니다. | 1234567890 |
reward_amount | 광고 단위 설정에 지정된 보상 금액입니다. | 5 |
reward_item | 광고 단위 설정에 지정된 대로 보상을 합니다. | 동전 |
서명 | AdMob에서 생성한 SSV 콜백 서명입니다. | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
타임스탬프 | 사용자가 보상을 받은 시간의 타임스탬프입니다(에포크 시간, ms). | 1507770365237823 |
transaction_id | AdMob에서 생성된 각 보상 이벤트의 고유한 16진수 코드 식별자입니다. | 18fa792de1bca816048293fc71035638 |
user_id | setUserId 에서 제공한 사용자 식별자입니다.
앱에서 사용자 식별자를 제공하지 않으면 이 쿼리 매개변수는 SSV 콜백에 표시되지 않습니다. |
1234567 |
광고 소스 식별자
광고 소스 이름 및 ID
광고 소스 이름 | 광고 소스 ID |
---|---|
Aarki (입찰) | 5240798063227064260 |
광고 생성 (입찰) | 1477265452970951479 |
AdColony | 15586990674969969776 |
AdColony (비 SDK) (입찰) | 4600416542059544716 |
AdColony (입찰) | 6895345910719072481 |
AdFalcon | 3528208921554210682 |
AdMob 네트워크 | 5450213213286189855 |
AdMob 네트워크 폭포식 구조 | 1215381445328257950 |
ADResult | 10593873382626181482 |
AMoAd | 17253994435944008978 |
Applovin | 1063618907739174004 |
Applovin (입찰) | 1328079684332308356 |
Chartboost | 2873236629771172317 |
Chocolate Platform (입찰) | 6432849193975106527 |
CrossChannel (MdotM) | 9372067028804390441 |
맞춤 이벤트 | 18351550913290782395 |
DT Exchange* * 2022년 9월 21일 이전에는 이 네트워크를 'Fyber Marketplace'라고 했습니다. | 2179455223494392917 |
EMX (입찰) | 8497809869790333482 |
Fluct (입찰) | 8419777862490735710 |
Flurry | 3376427960656545613 |
Fyber* * 이 광고 소스는 이전 보고에 사용됩니다. | 4839637394546996422 |
i-mobile | 5208827440166355534 |
Improve Digital (입찰) | 159382223051638006 |
Index Exchange (입찰) | 4100650709078789802 |
InMobi | 7681903010231960328 |
InMobi (입찰) | 6325663098072678541 |
InMobi Exchange (입찰) | 5264320421916134407 |
IronSource | 6925240245545091930 |
ironSource Ads (입찰) | 1643326773739866623 |
Leadbolt | 2899150749497968595 |
LG U+AD | 18298738678491729107 |
LINE Ads Network | 3025503711505004547 |
maio | 7505118203095108657 |
maio (입찰) | 1343336733822567166 |
Media.net (입찰) | 2127936450554446159 |
미디에이션된 하우스 광고 | 6060308706800320801 |
Meta Audience Network* * 2022년 6월 6일 이전에는 이 네트워크를 'Facebook Audience Network'라 했습니다. | 10568273599589928883 |
Meta Audience Network (입찰)* * 2022년 6월 6일 이전에는 이 네트워크를 'Facebook Audience Network (입찰)'라 했습니다. | 11198165126854996598 |
Mintegral | 1357746574408896200 |
Mintegral (입찰) | 6250601289653372374 |
MobFox | 8079529624516381459 |
MobFox (입찰) | 3086513548163922365 |
MoPub (지원 중단됨) | 10872986198578383917 |
myTarget | 8450873672465271579 |
Nend | 9383070032774777750 |
Nexxen (입찰)* * 2024년 5월 1일 이전에는 이 네트워크를 'UnrulyX'라고 했습니다. | 2831998725945605450 |
ONE by AOL (Millennial Media) | 6101072188699264581 |
ONE by AOL (Nexage) | 3224789793037044399 |
OneTag Exchange (입찰) | 4873891452523427499 |
OpenX (입찰) | 4918705482605678398 |
Pangle | 4069896914521993236 |
Pangle (입찰) | 3525379893916449117 |
PubMatic (입찰) | 3841544486172445473 |
예약 캠페인 | 7068401028668408324 |
RhythmOne (입찰) | 2831998725945605450 |
Rubicon (입찰) | 3993193775968767067 |
SK planet | 734341340207269415 |
Sharethrough (입찰) | 5247944089976324188 |
Smaato (입찰) | 3362360112145450544 |
Equativ (입찰)* * 2023년 1월 12일 이전에는 이 네트워크를 'Smart Adserver'라고 했습니다. | 5970199210771591442 |
Sonobi (입찰) | 3270984106996027150 |
Tapjoy | 7295217276740746030 |
Tapjoy (입찰) | 4692500501762622178 |
Tencent GDT | 7007906637038700218 |
TripleLift (입찰) | 8332676245392738510 |
Unity Ads | 4970775877303683148 |
Unity Ads (입찰) | 7069338991535737586 |
Verizon Media | 7360851262951344112 |
Verve Group (입찰) | 5013176581647059185 |
Vpon | 1940957084538325905 |
Liftoff Monetize* * 2023년 1월 30일 이전에는 이 네트워크를 'Vungle'이라 했습니다. | 1953547073528090325 |
Liftoff Monetize (입찰)* * 2023년 1월 30일 이전에는 이 네트워크를 'Vungle (입찰)'이라 했습니다. | 4692500501762622185 |
Yieldmo (입찰) | 4193081836471107579 |
YieldOne (입찰) | 3154533971590234104 |
Zucks | 5506531810221735863 |
사용자 보상
사용자에게 보상해야 할 시점을 정할 때 사용자 경험과 보상 검증의 균형을 맞추는 것이 중요합니다. 서버 측 콜백은 외부 시스템에 도달하기 전에 지연이 발생할 수 있습니다. 따라서 클라이언트 측 콜백을 사용하여 즉시 사용자에게 보상하고 서버 측 콜백을 받는 즉시 모든 보상에 대한 유효성 검사를 하는 것이 좋습니다. 이 방법에서는 보상의 유효성을 확인하는 동안 우수한 사용자 환경을 제공합니다.
그러나 보상이 앱 게임 내의 경제에 영향을 주는 등 보상 유효성이 앱에 매우 중요하고 보상 지급 지연이 허용되는 경우에는 인증된 서버 측 콜백을 기다리는 것이 가장 좋습니다.
맞춤 데이터
서버 측 확인 콜백에서 추가 데이터가 필요한 앱은 보상형 광고의 맞춤 데이터 기능을 사용해야 합니다. 보상형 광고 객체에 설정된 모든 문자열 값은 SSV 콜백의 custom_data
쿼리 매개변수에 전달됩니다. 맞춤 데이터 값이 설정되지 않은 경우 custom_data
쿼리 매개변수 값은 SSV 콜백에 표시되지 않습니다.
다음 예에서는 보상형 광고가 로드된 후 SSV 옵션을 설정합니다.
자바
RewardedAd.load(MainActivity.this, "AD_UNIT_ID", new AdRequest.Builder().build(), new RewardedAdLoadCallback() { @Override public void onAdLoaded(RewardedAd ad) { Log.d(TAG, "Ad was loaded."); rewardedAd = ad; ServerSideVerificationOptions options = new ServerSideVerificationOptions .Builder() .setCustomData("SAMPLE_CUSTOM_DATA_STRING") .build(); rewardedAd.setServerSideVerificationOptions(options); } @Override public void onAdFailedToLoad(LoadAdError loadAdError) { Log.d(TAG, loadAdError.toString()); rewardedAd = null; } });
Kotlin
RewardedAd.load(this, "AD_UNIT_ID", AdRequest.Builder().build(), object : RewardedAdLoadCallback() { override fun onAdLoaded(ad: RewardedAd) { Log.d(TAG, "Ad was loaded.") rewardedInterstitialAd = ad val options = ServerSideVerificationOptions.Builder() .setCustomData("SAMPLE_CUSTOM_DATA_STRING") .build() rewardedAd.setServerSideVerificationOptions(options) } override fun onAdFailedToLoad(adError: LoadAdError) { Log.d(TAG, adError?.toString()) rewardedAd = null } })
광고를 게재하기 전에 맞춤 보상 문자열을 설정해야 합니다.
보상형 SSV의 수동 인증
RewardedAdsVerifier
클래스가 보상형 SSV를 인증하기 위해 수행하는 단계는 아래에 나와 있습니다. 포함된 코드 스니펫이 자바로 되어 있고
Tink 서드 파티 라이브러리를 활용해도 이 단계는 ECDSA를 지원하는 서드 파티 라이브러리를 통해 원하는 언어로 구현할 수 있습니다.
공개 키 가져오기
보상형 SSV 콜백을 인증하려면 AdMob에서 제공하는 공개 키가 필요합니다.
보상형 SSV 콜백을 확인하는 데 사용되는 공개 키의 목록은 AdMob 키 서버에서 가져올 수 있습니다. 공개 키 목록은 다음과 비슷한 형식의 JSON 표현으로 제공됩니다.
{
"keys": [
{
keyId: 1916455855,
pem: "-----BEGIN PUBLIC KEY-----\nMF...YTPcw==\n-----END PUBLIC KEY-----"
base64: "MFkwEwYHKoZIzj0CAQYI...ltS4nzc9yjmhgVQOlmSS6unqvN9t8sqajRTPcw=="
},
{
keyId: 3901585526,
pem: "-----BEGIN PUBLIC KEY-----\nMF...aDUsw==\n-----END PUBLIC KEY-----"
base64: "MFYwEAYHKoZIzj0CAQYF...4akdWbWDCUrMMGIV27/3/e7UuKSEonjGvaDUsw=="
},
],
}
공개 키를 검색하려면 AdMob 키 서버에 연결하고 키를 다운로드하세요. 다음 코드에서는 이 작업을 처리하고 키의 JSON 표현을
data
변수에 저장합니다.
String url = ...;
NetHttpTransport httpTransport = new NetHttpTransport.Builder().build();
HttpRequest httpRequest =
httpTransport.createRequestFactory().buildGetRequest(new GenericUrl(url));
HttpResponse httpResponse = httpRequest.execute();
if (httpResponse.getStatusCode() != HttpStatusCodes.STATUS_CODE_OK) {
throw new IOException("Unexpected status code = " + httpResponse.getStatusCode());
}
String data;
InputStream contentStream = httpResponse.getContent();
try {
InputStreamReader reader = new InputStreamReader(contentStream, UTF_8);
data = readerToString(reader);
} finally {
contentStream.close();
}
공개 키는 정기적으로 로테이션됩니다. 예정된 로테이션을 알리는 이메일이 발송됩니다. 공개 키를 캐싱하는 경우 이 이메일을 받으면 키를 업데이트해야 합니다.
가져온 공개 키는 파싱해야 합니다. 아래의 parsePublicKeysJson
메서드는 위의 예와 같은 JSON 문자열을 입력으로 사용하고 key_id
값을 공개 키로 매핑합니다. 이 공개 키는 Tink 라이브러리의 ECPublicKey
객체로 캡슐화됩니다.
private static Map<Integer, ECPublicKey> parsePublicKeysJson(String publicKeysJson)
throws GeneralSecurityException {
Map<Integer, ECPublicKey> publicKeys = new HashMap<>();
try {
JSONArray keys = new JSONObject(publicKeysJson).getJSONArray("keys");
for (int i = 0; i < keys.length(); i++) {
JSONObject key = keys.getJSONObject(i);
publicKeys.put(
key.getInt("keyId"),
EllipticCurves.getEcPublicKey(Base64.decode(key.getString("base64"))));
}
} catch (JSONException e) {
throw new GeneralSecurityException("failed to extract trusted signing public keys", e);
}
if (publicKeys.isEmpty()) {
throw new GeneralSecurityException("No trusted keys are available.");
}
return publicKeys;
}
인증할 콘텐츠 가져오기
보상형 SSV 콜백의 마지막 두 개 쿼리 매개변수는 항상 signature
와 key_id,
순서로 정렬됩니다. 나머지 쿼리 매개변수는 인증할 콘텐츠를 지정합니다. 보상 콜백을 https://www.myserver.com/mypath
로 보내도록 AdMob을 구성했다고 가정해 보겠습니다. 아래 스니펫은 인증할 콘텐츠(강조)가 포함된 보상형 SSV 콜백의 예를 보여줍니다.
https://www.myserver.com/path?ad_network=54...55&ad_unit=12345678&reward_amount=10&reward_item=coins ×tamp=150777823&transaction_id=12...DEF&user_id=1234567&signature=ME...Z1c&key_id=1268887
아래 코드는 인증할 내용을 UTF-8 바이트 배열로 콜백 URL에서 파싱하는 방법을 보여줍니다.
public static final String SIGNATURE_PARAM_NAME = "signature=";
...
URI uri;
try {
uri = new URI(rewardUrl);
} catch (URISyntaxException ex) {
throw new GeneralSecurityException(ex);
}
String queryString = uri.getQuery();
int i = queryString.indexOf(SIGNATURE_PARAM_NAME);
if (i == -1) {
throw new GeneralSecurityException("needs a signature query parameter");
}
byte[] queryParamContentData =
queryString
.substring(0, i - 1)
// i - 1 instead of i because of & in the query string
.getBytes(Charset.forName("UTF-8"));
콜백 URL에서 서명 및 key_id 가져오기
이전 단계의 queryString
값을 사용하여 아래와 같이 콜백 URL의 signature
및 key_id
쿼리 매개변수를 파싱합니다.
public static final String KEY_ID_PARAM_NAME = "key_id=";
...
String sigAndKeyId = queryString.substring(i);
i = sigAndKeyId.indexOf(KEY_ID_PARAM_NAME);
if (i == -1) {
throw new GeneralSecurityException("needs a key_id query parameter");
}
String sig =
sigAndKeyId.substring(
SIGNATURE_PARAM_NAME.length(), i - 1 /* i - 1 instead of i because of & */);
int keyId = Integer.valueOf(sigAndKeyId.substring(i + KEY_ID_PARAM_NAME.length()));
인증 처리
마지막은 공개 키로 콜백 URL의 내용을 확인하는 단계입니다. parsePublicKeysJson
메서드에서 반환된 매핑을 가져와서
콜백 URL의 key_id
매개변수를 통해
해당 매핑의 공개 키를 가져옵니다. 그런 다음 공개 키로 서명을 확인합니다. 아래의 verify
메서드에 이 단계에 대한 설명이 나와 있습니다.
private void verify(final byte[] dataToVerify, int keyId, final byte[] signature)
throws GeneralSecurityException {
Map<Integer, ECPublicKey> publicKeys = parsePublicKeysJson();
if (publicKeys.containsKey(keyId)) {
foundKeyId = true;
ECPublicKey publicKey = publicKeys.get(keyId);
EcdsaVerifyJce verifier = new EcdsaVerifyJce(publicKey, HashType.SHA256, EcdsaEncoding.DER);
verifier.verify(signature, dataToVerify);
} else {
throw new GeneralSecurityException("cannot find verifying key with key ID: " + keyId);
}
}
메서드가 예외 없이 실행되면 콜백 URL이 성공적으로 인증된 것입니다.
FAQ
- AdMob 키 서버에서 제공하는 공개 키를 캐시할 수 있나요?
- SSV 콜백을 확인하는 데 필요한 작업 수를 줄이려면 AdMob 키 서버에서 제공하는 공개 키를 캐시하는 것이 좋습니다. 그러나 공개 키는 정기적으로 로테이션되며 24시간 이상 캐시해서는 안 됩니다.
- AdMob 키 서버에서 제공하는 공개 키는 얼마나 자주 로테이션되나요?
- AdMob 키 서버에서 제공하는 공개 키는 다양한 일정으로 로테이션됩니다. SSV 콜백 확인이 의도한 대로 계속 작동하려면 공개 키가 24시간 이상 캐시되지 않아야 합니다.
- 서버에 연결할 수 없는 경우 어떻게 되나요?
- Google은 SSV 콜백에 대해
HTTP 200 OK
성공 상태 응답 코드를 예상합니다. 서버에 연결할 수 없거나 서버에서 예상되는 응답을 제공하지 않으면 Google은 1초 간격으로 최대 5회 SSV 콜백을 전송하려고 재시도합니다. - SSV 콜백이 Google에서 전송되는지 확인하려면 어떻게 해야 하나요?
- SSV 콜백이 Google에서 전송되는지 확인하려면 역방향 DNS 조회를 사용하세요.