伺服器端驗證回呼是 Google 擴充查詢參數的網址要求,Google 會將這些要求傳送至外部系統,通知系統應為使用者提供獎勵,因為使用者與獎勵影片或獎勵插頁式廣告互動。獎勵 SSV (伺服器端驗證) 回呼可提供額外一層防護,防止用戶端回呼遭到冒用。
本指南說明如何使用 Tink Java Apps 第三方加密程式庫,驗證獎勵式 SSV 回呼,確保回呼中的查詢參數為合法值。雖然本指南使用 Tink,但您可以選擇使用任何支援 ECDSA 的第三方程式庫。您也可以使用 AdMob UI 中的測試工具測試伺服器。
必要條件
- 在廣告單元中啟用獎勵廣告的伺服器端驗證。
使用 Tink Java Apps 程式庫中的 RewardedAdsVerifier
Tink Java 應用程式 GitHub 存放區包含 RewardedAdsVerifier
輔助類別,可減少驗證獎勵式 SSV 回呼所需的程式碼。使用這個類別,您就能使用下列程式碼驗證回呼網址。
RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
.fetchVerifyingPublicKeysWith(
RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
.build();
String rewardUrl = ...;
verifier.verify(rewardUrl);
如果 verify()
方法執行時未擲回例外狀況,表示已成功驗證回呼網址。「獎勵使用者」一節會詳細說明應在何時獎勵使用者,以及相關最佳做法。如要瞭解這個類別執行的步驟,以便驗證獎勵 SSV 回呼,請參閱「手動驗證獎勵 SSV」一節。
SSV 回呼參數
伺服器端驗證回呼包含描述獎勵廣告互動的查詢參數。以下列出參數名稱、說明和範例值。參數會依字母順序傳送。
參數名稱 | 說明 | 範例值 |
---|---|---|
ad_network | 這個廣告的廣告來源 ID。對應 ID 值的廣告來源名稱會列在「廣告來源 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 |
時間戳記 | 使用者獲得獎勵的時間戳記,以 Epoch 時間為單位 (以毫秒為單位)。 | 1507770365237823 |
transaction_id | 由 AdMob 產生的每個獎勵授予事件專屬的 16 進位編碼 ID。 | 18fa792de1bca816048293fc71035638 |
user_id | 由 userIdentifier 提供的使用者 ID。如果應用程式未提供使用者 ID,SSV 回呼中就不會出現這個查詢參數。 |
1234567 |
廣告來源 ID
廣告來源名稱和 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 (bidding) | 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 (bidding)* * 在 2022 年 6 月 6 日前,這個聯播網稱為「Facebook Audience Network (bidding)」。 | 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 行星 | 734341340207269415 |
Sharethrough (出價) | 5247944089976324188 |
Smaato (出價) | 3362360112145450544 |
Equativ (出價)* * 在 2023 年 1 月 12 日前,這個聯播網稱為「Smart Adserver」。 | 5970199210771591442 |
Sonobi (出價) | 3270984106996027150 |
Tapjoy | 7295217276740746030 |
Tapjoy (出價) | 4692500501762622178 |
騰訊 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
查詢參數。如果未設定自訂資料值,SSV 回呼中就不會顯示 custom_data
查詢參數值。
以下範例會在獎勵廣告載入後設定 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 的步驟。雖然附帶的程式碼片段是 Java 程式碼,且會運用 Tink 第三方程式庫,但您可以使用任何支援 ECDSA 的第三方程式庫,以所選語言實作這些步驟。
擷取公開金鑰
如要驗證獎勵式 SSV 回呼,您需要 AdMob 提供的公開金鑰。
您可以從 AdMob 金鑰伺服器擷取用於驗證獎勵式 SSV 回呼的公開金鑰清單。公開金鑰清單會以 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,
。其餘查詢參數則會指定要驗證的內容。假設您已將 AdMob 設為將獎勵回呼傳送至 https://www.myserver.com/mypath
。以下程式碼片段顯示獎勵式 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 位元組陣列。
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"));
從回呼網址取得簽章和 key_id
使用上一個步驟的 queryString
值,剖析回呼網址中的 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()));
執行驗證
最後一個步驟是使用適當的公開金鑰驗證回呼網址內容。請取得 parsePublicKeysJson
方法傳回的對應項目,並使用回呼網址中的 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);
}
}
如果方法執行時不會擲回例外狀況,表示已成功驗證回呼網址。
常見問題
- 我可以將 AdMob 金鑰伺服器提供的公開金鑰快取嗎?
- 建議您將 AdMob 金鑰伺服器提供的公開金鑰快取,以減少驗證 SSV 回呼所需的作業數量。不過,請注意,公開金鑰會定期輪替,因此不應在快取中保留超過 24 小時。
- AdMob 金鑰伺服器提供的公開金鑰旋轉頻率為何?
- AdMob 金鑰伺服器提供的公開金鑰會依照變化時間表輪替。為確保 SSV 回呼的驗證功能能繼續正常運作,公開金鑰的快取時間不應超過 24 小時。
- 如果無法連線至伺服器,會發生什麼情況?
- Google 預期 SSV 回呼的狀態回應碼為
HTTP 200 OK
成功。如果無法連線至您的伺服器,或伺服器未提供預期的回應,Google 會在 1 秒的間隔內,最多嘗試傳送 SSV 回呼五次。 - 如何確認 SSV 回呼來自 Google?
- 使用反向 DNS 查詢,驗證 SSV 回呼是否來自 Google。