驗證伺服器端驗證 (SSV) 回呼

伺服器端驗證回呼是指網址由 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
AdColony15586990674969969776
AdColony (非 SDK) (出價)4600416542059544716
AdColony (出價)6895345910719072481
AdFalcon3528208921554210682
AdMob 聯播網5450213213286189855
廣告結果10593873382626181482
AMoAd17253994435944008978
愛洛維尼亞1063618907739174004
Applovin (出價)1328079684332308356
Chartboost2873236629771172317
巧克力平台 (出價)6432849193975106527
跨管道 (MdotM)9372067028804390441
自訂事件18351550913290782395
DT Exchange*
* 2022 年 9 月 21 日以前,這個聯播網的名稱為「Fyber Marketplace」。
2179455223494392917
Fluct (出價)8419777862490735710
微風3376427960656545613
Fyber*
* 這個廣告來源用於歷來資料報表。
4839637394546996422
i-mobile5208827440166355534
改善數位出價 (出價)159382223051638006
Index Exchange (出價)4100650709078789802
InMobi7681903010231960328
InMobi (出價)6325663098072678541
IronSource6925240245545091930
Leadbolt2899150749497968595
LG U+AD18298738678491729107
Maio7505118203095108657
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
MobFox8079529624516381459
MoPub (已淘汰)10872986198578383917
我的目標8450873672465271579
Nend9383070032774777750
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
Tapjoy7295217276740746030
Tapjoy (出價)4692500501762622178
騰訊 GDT7007906637038700218
TripleLift (出價)8332676245392738510
Unity 廣告4970775877303683148
UnrulyX (出價)2831998725945605450
Verizon Media7360851262951344112
VPN1940957084538325905
利潤營利*

* 如果在 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 回呼的最後兩個查詢參數,依序是 signaturekey_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
&timestamp=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 值,從回呼網址剖析 signaturekey_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。