伺服器端驗證回呼是指網址由 Google 擴展的查詢參數,這些請求由 Google 傳送至外部系統,以通知使用者應與獎勵或獎勵插頁式廣告互動。獎勵廣告 SSV (伺服器端驗證) 回呼提供額外一層保護,可防範用戶端回呼,以獎勵使用者。
本指南說明如何使用 Tink Java Apps 第三方密碼編譯程式庫驗證獎勵的 SSV 回呼,以確保回呼中的查詢參數是合法的值。本指南使用 Tink 的目的在於,您可以使用任何支援 ECDSA 的第三方程式庫。您也可以使用 AdMob UI 中的測試工具來測試伺服器。
請使用 Java Spring-boot 查看這個完全正常運作的範例。
必要條件
使用 Tink Java Apps Library 中的 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 |
自訂資料 | 由 提供的自訂資料字串
setCustomData
.
如果應用程式未提供自訂資料字串,此 SSV 回呼就不會顯示此查詢參數值。 |
SAMPLE_CUSTOM_DATA_STRING |
key_id | 用於驗證 SSV 回呼的金鑰。這個值會對應至 AdMob 金鑰伺服器提供的公開金鑰。 | 1234567890 |
獎勵金額 | 廣告單元設定中指定的獎勵金額。 | 5 |
獎勵商品 | 廣告單元設定中指定的獎勵項目。 | 硬幣 |
簽名 | AdMob 產生的 SSV 回呼簽名。 | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
時間戳記 | 使用者獲得獎勵的時間,以毫秒為單位,以毫秒為單位計算。 | 1507770365237823 |
transaction_id | 由 AdMob 產生的每個獎勵補助活動專屬的十六進位編碼 ID。 | 18fa792de1bca816048293fc71035638 |
user_id | 由
setUserId 提供的使用者 ID。
如果應用程式未提供使用者 ID,此查詢參數就不會顯示在 SSV 回呼中。 |
1234567 |
廣告來源 ID
廣告來源名稱和 ID
廣告來源名稱 | 廣告來源 ID |
---|---|
Aarki (出價) | 5240798063227064260 |
廣告產生 (出價) | 1477265452970951479 |
AdColony | 15586990674969969776 |
AdColony (非 SDK) (出價) | 4600416542059544716 |
AdColony (出價) | 6895345910719072481 |
AdFalcon | 3528208921554210682 |
AdMob 聯播網 | 5450213213286189855 |
廣告結果 | 10593873382626181482 |
AMoAd | 17253994435944008978 |
愛洛維尼亞 | 1063618907739174004 |
Applovin (出價) | 1328079684332308356 |
Chartboost | 2873236629771172317 |
巧克力平台 (出價) | 6432849193975106527 |
跨管道 (MdotM) | 9372067028804390441 |
自訂事件 | 18351550913290782395 |
DT Exchange* * 2022 年 9 月 21 日以前,這個聯播網的名稱為「Fyber Marketplace」。 | 2179455223494392917 |
Fluct (出價) | 8419777862490735710 |
微風 | 3376427960656545613 |
Fyber* * 這個廣告來源用於歷來資料報表。 | 4839637394546996422 |
i-mobile | 5208827440166355534 |
改善數位出價 (出價) | 159382223051638006 |
Index Exchange (出價) | 4100650709078789802 |
InMobi | 7681903010231960328 |
InMobi (出價) | 6325663098072678541 |
IronSource | 6925240245545091930 |
Leadbolt | 2899150749497968595 |
LG U+AD | 18298738678491729107 |
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 |
MobFox | 8079529624516381459 |
MoPub (已淘汰) | 10872986198578383917 |
我的目標 | 8450873672465271579 |
Nend | 9383070032774777750 |
ONE by AOL (千禧世代媒體) | 6101072188699264581 |
ONE by AOL (Nexage) | 3224789793037044399 |
OpenX (出價) | 4918705482605678398 |
Pangle (出價) | 3525379893916449117 |
PubMatic (出價) | 3841544486172445473 |
預訂廣告活動 | 7068401028668408324 |
RhythmOne (出價) | 2831998725945605450 |
Rubicon (出價) | 3993193775968767067 |
SK 行星 | 734341340207269415 |
瀏覽後轉換 (出價) | 5247944089976324188 |
Smaato (出價) | 3362360112145450544 |
Equativ (出價)* * 在 2023 年 1 月 12 日前,這個聯播網稱為「智慧型廣告伺服器」。 | 5970199210771591442 |
Sonobi (出價) | 3270984106996027150 |
Tapjoy | 7295217276740746030 |
Tapjoy (出價) | 4692500501762622178 |
騰訊 GDT | 7007906637038700218 |
TripleLift (出價) | 8332676245392738510 |
Unity 廣告 | 4970775877303683148 |
UnrulyX (出價) | 2831998725945605450 |
Verizon Media | 7360851262951344112 |
VPN | 1940957084538325905 |
利潤營利* * 如果在 2023 年 1 月 30 日前,這個網路稱為「Vungle」。 | 1953547073528090325 |
利潤營利 (出價)* * 此聯播網在 2023 年 1 月 30 日以前稱為「Vungle (出價)」。 | 4692500501762622185 |
Yieldmo (出價) | 4193081836471107579 |
YieldOne (出價) | 3154533971590234104 |
貨車 | 5506531810221735863 |
獎勵使用者
決定向使用者提供獎勵的時機,在使用者體驗和獎勵驗證之間取得平衡是很重要的。伺服器端回呼可能會在連線至外部系統之前發生延遲。因此,建議的最佳做法是使用用戶端回呼來立即獎勵使用者,同時會在收到伺服器端回呼時對所有獎勵執行驗證。這種做法可提供良好的使用者體驗,同時確保已發放獎勵的有效性。
然而,對於獎勵有效性 (例如獎勵會影響應用程式的遊戲內經濟生態) 且提供獎勵的延遲時間較短,則應用程式可接受,因此等待已驗證的伺服器端回呼可能是最好的方法。
自訂資料
如果應用程式需要在伺服器端驗證回呼中加入額外資料,則應使用獎勵廣告的自訂資料功能。獎勵廣告物件上設定的任何字串值都會傳送至 SSV 回呼的 custom_data
查詢參數。如未設定自訂資料值,SSV 回呼就不會顯示 custom_data
查詢參數值。
以下程式碼範例示範如何在載入獎勵廣告之後,設定可擴充向量圖形選項。
Java
RewardedAd.load(MainActivity.this, "ca-app-pub-3940256099942544/5354046379", 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, "ca-app-pub-3940256099942544/5354046379", 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 } })
如要設定自訂獎勵字串,您必須先完成這項操作才能顯示廣告。
手動驗證獎勵可擴充向量圖形
以下說明 RewardedAdsVerifier
類別執行的獎勵 SSV 步驟。雖然隨附的程式碼片段位於 Java 中,並採用 Tink 第三方程式庫,但您可以依照您慣用的語言,使用支援 ECDSA 的第三方程式庫來實作這些步驟。
擷取公開金鑰
您需要 AdMob 提供的公開金鑰,才能驗證獎勵 SSV 回呼。
您可以從 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 會重新嘗試每秒鐘傳送一次 5 次 SSV 回呼。 - 如何驗證來自 Google 的 SSV 回呼?
- 透過反向 DNS 查詢驗證 Google 的 SSV 回呼是否來自 Google。