서버 측 인증 콜백은 Google에서 외부 시스템에 보내고 Google에서 확장하는 쿼리 매개변수가 포함된 URL 요청입니다. 이를 통해 보상형 광고 또는 보상형 전면 광고와의 상호작용에 대해 사용자에게 보상을 제공해야 함을 알립니다. 보상형 SSV (서버 측 인증) 콜백은 클라이언트 측 콜백 스푸핑에 대한 추가 보호 계층을 제공하여 사용자에게 보상합니다.
이 가이드에서는 Tink Java 앱 서드 파티 암호화 라이브러리를 사용하여 보상형 SSV 콜백을 확인하여 콜백의 쿼리 매개변수가 올바른 값인지 확인하는 방법을 설명합니다. 이 가이드에서는 Tink를 사용하지만 ECDSA를 지원하는 서드 파티 라이브러리를 사용할 수 있습니다. AdMob UI에서 테스트 도구를 사용하여 서버를 테스트할 수도 있습니다.
기본 요건
- 광고 단위에서 보상형 서버 측 확인을 사용 설정하세요.
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 |
customRewardString 에서 제공한 맞춤 데이터 문자열입니다.
앱에서 맞춤 데이터 문자열을 제공하지 않으면 이 쿼리 매개변수 값이 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 | userIdentifier 에서 제공한 사용자 식별자입니다.
앱에서 사용자 식별자를 제공하지 않으면 이 쿼리 매개변수는 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 옵션을 설정합니다.
Swift
GADRewardedAd.load(withAdUnitID:"AD_UNIT_ID", request: request, completionHandler: { [self] ad, error in if let error != error { rewardedAd = ad let options = GADServerSideVerificationOptions() options.customRewardString = "SAMPLE_CUSTOM_DATA_STRING" rewardedAd.serverSideVerificationOptions = options }
Objective-C
GADRequest *request = [GADRequest request]; [GADRewardedAd loadWithAdUnitID:@"AD_UNIT_ID" request:request completionHandler:^(GADRewardedAd *ad, NSError *error) { if (error) { // Handle Error return; } self.rewardedAd = ad; GADServerSideVerificationOptions *options = [[GADServerSideVerificationOptions alloc] init]; options.customRewardString = @"SAMPLE_CUSTOM_DATA_STRING"; ad.serverSideVerificationOptions = options; }];
보상형 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 조회를 사용하세요.