伺服器端驗證回呼是網址要求,其中包含 Google 擴充的查詢參數,Google 會將這類要求傳送至外部系統,通知系統使用者應因與獎勵或獎勵插頁式廣告互動而獲得獎勵。獎勵伺服器端驗證 (SSV) 回呼可提供額外防護,防止用戶端回呼遭冒用,以獎勵使用者。
本指南說明如何使用 Tink Java 應用程式第三方加密程式庫,驗證 SSV 回呼中的查詢參數是否為合法值,確保回呼的有效性。雖然本指南使用 Tink,但您可以選擇使用任何支援 ECDSA 的第三方程式庫。您也可以使用 AdMob 使用者介面的測試工具測試伺服器。
必要條件
- 在廣告單元上啟用獎勵伺服器端驗證。
使用 Tink Java 應用程式程式庫中的 RewardedAdsVerifier
Tink Java Apps 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 |
timestamp | 使用者獲得獎勵的時間戳記,以 Epoch 時間表示 (毫秒為單位)。 | 1507770365237823 |
transaction_id | AdMob 產生的每個獎勵授予事件的專屬十六進位編碼 ID。 | 18fa792de1bca816048293fc71035638 |
user_id | 由
userIdentifier 提供的使用者 ID。
如果應用程式未提供任何使用者 ID,SSV 回呼中就不會出現這個查詢參數。 |
1234567 |
廣告來源 ID
廣告來源名稱和 ID
廣告來源名稱 | 廣告來源 ID |
---|---|
Ad Generation (bidding) | 1477265452970951479 |
AdColony | 15586990674969969776 |
AdColony (出價) | 6895345910719072481 |
AdFalcon | 3528208921554210682 |
AdMob 聯播網 | 5450213213286189855 |
AdMob 聯播網刊登序列 | 1215381445328257950 |
Applovin | 1063618907739174004 |
Applovin (出價) | 1328079684332308356 |
Chartboost | 2873236629771172317 |
Chocolate Platform (出價) | 6432849193975106527 |
自訂事件 | 18351550913290782395 |
DT Exchange* * 2022 年 9 月 21 日前,這個聯播網稱為「Fyber Marketplace」。 | 2179455223494392917 |
Equativ (出價)* * 2023 年 1 月 12 日前,這個聯播網稱為「Smart Adserver」。 | 5970199210771591442 |
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 |
Liftoff Monetize* * 2023 年 1 月 30 日前,這個廣告聯盟稱為「Vungle」。 | 1953547073528090325 |
Liftoff Monetize (出價)* * 2023 年 1 月 30 日前,這個聯播網稱為「Vungle (出價)」。 | 4692500501762622185 |
LG U+AD | 18298738678491729107 |
LINE Ads Network | 3025503711505004547 |
Magnite DV+ (出價) | 3993193775968767067 |
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 (出價) | 3086513548163922365 |
MoPub (已淘汰) | 10872986198578383917 |
myTarget | 8450873672465271579 |
Nend | 9383070032774777750 |
Nexxen (出價)* * 2024 年 5 月 1 日前,這個聯播網稱為「UnrulyX」。 | 2831998725945605450 |
OneTag Exchange (出價) | 4873891452523427499 |
OpenX (出價) | 4918705482605678398 |
Pangle | 4069896914521993236 |
Pangle (出價) | 3525379893916449117 |
PubMatic (出價) | 3841544486172445473 |
預訂廣告活動 | 7068401028668408324 |
SK planet | 734341340207269415 |
Sharethrough (出價) | 5247944089976324188 |
Smaato (出價) | 3362360112145450544 |
Sonobi (出價) | 3270984106996027150 |
Tapjoy | 7295217276740746030 |
Tapjoy (出價) | 4692500501762622178 |
Tencent GDT | 7007906637038700218 |
TripleLift (出價) | 8332676245392738510 |
Unity Ads | 4970775877303683148 |
Unity Ads (出價) | 7069338991535737586 |
Verve Group (出價) | 5013176581647059185 |
Vpon | 1940957084538325905 |
Yieldmo (出價) | 4193081836471107579 |
YieldOne (出價) | 3154533971590234104 |
Zucks | 5506531810221735863 |
獎勵使用者
決定何時要獎勵使用者時,請務必兼顧使用者體驗和獎勵驗證。伺服器端回呼可能需要一段時間,才會傳送至外部系統。因此,建議的最佳做法是使用用戶端回呼立即獎勵使用者,同時在收到伺服器端回呼時,驗證所有獎勵。這種做法可確保授予的獎勵有效,同時提供良好的使用者體驗。
不過,如果獎勵有效性至關重要 (例如獎勵會影響應用程式的遊戲內經濟),且可接受獎勵發放延遲,則等待經過驗證的伺服器端回呼可能是最佳做法。
自訂資料
如果應用程式需要在伺服器端驗證回呼中取得額外資料,請使用獎勵廣告的自訂資料功能。在獎勵廣告物件上設定的任何字串值,都會傳遞至 SSV 回呼的 custom_data
查詢參數。如果未設定任何自訂資料值,SSV 回呼中就不會出現 custom_data
查詢參數值。
以下範例會在載入獎勵廣告後設定 SSV 選項:
Swift
RewardedAd.load(with:"AD_UNIT_ID", request: request, completionHandler: { [self] ad, error in if let error != error { rewardedAd = ad let options = ServerSideVerificationOptions() 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 提供的公開金鑰。
如要驗證有獎 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,
,且順序固定。其餘查詢參數則會指定要驗證的內容。假設您已將 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 會以一秒為間隔,最多重試傳送 SSV 回呼五次。 - 如何確認 SSV 回呼來自 Google?
- 使用反向 DNS 查詢,確認 SSV 回呼是否來自 Google。