伺服器端驗證回呼是網址要求 (由 Google 展開),並由 Google 傳送至外部系統,以通知使用者應獎勵使用者與獎勵廣告或插頁式獎勵廣告互動。獎勵 SSV (伺服器端驗證) 回呼提供額外一層防護,避免假冒用戶端回呼,以獎勵使用者。
本指南說明如何使用 Tink Java 應用程式第三方密碼編譯程式庫驗證獎勵 SSV 回呼,確保回呼中的查詢參數是正當值。雖然 Tink 是基於本指南的目的,但您也可以選擇使用任何支援 ECDSA 的第三方程式庫。您也可以使用 AdMob UI 中的測試工具測試伺服器。
查看使用 Java 彈簧啟動的完整有效範例。
必要條件
使用 Google 行動廣告 Unity 外掛程式 3.12.0 以上版本。
為廣告單元啟用獎勵伺服器端驗證。
使用 Tink Java 應用程式程式庫中的 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 | 自訂資料字串,提供者:
。
如果應用程式未提供自訂資料字串,SSV 回呼就不會顯示這個查詢參數值。 |
SAMPLE_CUSTOM_DATA_STRING |
key_id | 用來驗證 SSV 回呼的金鑰。這個值會對應至 AdMob 金鑰伺服器提供的公開金鑰。 | 1234567890 |
reward_amount | 廣告單元設定中指定的獎勵金額。 | 5 |
reward_item | 廣告單元設定中指定的獎勵項目。 | 硬幣 |
簽名 | AdMob 產生的 SSV 回呼簽章。 | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
時間戳記 | 使用者獲得獎勵時的時間戳記 (以毫秒為單位)。 | 1507770365237823 |
transaction_id | AdMob 產生的每個獎勵補助活動專屬十六進位編碼 ID。 | 18fa792de1bca816048293fc71035638 |
user_id | 由
SetUserId 。
如果應用程式未提供使用者 ID,SSV 回呼就不會顯示這個查詢參數。 |
1234567 |
廣告來源 ID
廣告來源名稱和 ID
廣告來源名稱 | 廣告來源 ID |
---|---|
Aarki (出價) | 5240798063227064260 |
產生 Ad Manager (出價) | 1477265452970951479 |
AdColony | 15586990674969969776 |
AdColony (非 SDK) (出價) | 4600416542059544716 |
AdColony (出價) | 6895345910719072481 |
AdFalcon | 3528208921554210682 |
AdMob 聯播網 | 5450213213286189855 |
ADResult | 10593873382626181482 |
AMoAd | 17253994435944008978 |
應用程式洛溫 | 1063618907739174004 |
AppLovin (出價) | 1328079684332308356 |
查特堡 | 2873236629771172317 |
巧克力平台 (出價) | 6432849193975106527 |
跨管道 (MdotM) | 9372067028804390441 |
自訂事件 | 18351550913290782395 |
DT Exchange* * 這個聯播網在 2022 年 9 月 21 日前稱為「Fyber Marketplace」。 | 2179455223494392917 |
EMX (出價) | 8497809869790333482 |
波動 (出價) | 8419777862490735710 |
飄逸袖 | 3376427960656545613 |
Fyber* * 這個廣告來源是用於歷來報表。 | 4839637394546996422 |
i-mobile | 5208827440166355534 |
改善數位 (出價) | 159382223051638006 |
Index Exchange (出價) | 4100650709078789802 |
InMobi | 7681903010231960328 |
InMobi (出價) | 6325663098072678541 |
IronSource | 6925240245545091930 |
ironSource 廣告 (出價) | 1643326773739866623 |
Leadbolt | 2899150749497968595 |
LG U+ 廣告 | 18298738678491729107 |
LINE 廣告聯播網 | 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 |
礦石 | 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 (出價) | 3525379893916449117 |
PubMatic (出價) | 3841544486172445473 |
預訂廣告活動 | 7068401028668408324 |
RhythmOne (出價) | 2831998725945605450 |
Rubicon (出價) | 3993193775968767067 |
SK 星球 | 734341340207269415 |
分享 (出價) | 5247944089976324188 |
Smaato (出價) | 3362360112145450544 |
等於 (出價)* * 這個聯播網在 2023 年 1 月 12 日前稱為「Smart Adserver」。 | 5970199210771591442 |
Sonobi (出價) | 3270984106996027150 |
Tapjoy | 7295217276740746030 |
Tapjoy (出價) | 4692500501762622178 |
騰訊 GDT | 7007906637038700218 |
TripleLift (出價) | 8332676245392738510 |
Unity 廣告 | 4970775877303683148 |
Unity 廣告 (出價) | 7069338991535737586 |
Verizon 媒體 | 7360851262951344112 |
Verve Group (出價) | 5013176581647059185 |
Vpon | 1940957084538325905 |
Liftoff Monetize* * 該聯播網在 2023 年 1 月 30 日之前稱為「Vungle」。 | 1953547073528090325 |
Liftoff Monetize (出價)* * 這個聯播網在 2023 年 1 月 30 日前稱為「Vungle (出價)」。 | 4692500501762622185 |
Yieldmo (出價) | 4193081836471107579 |
YieldOne (出價) | 3154533971590234104 |
查克斯 | 5506531810221735863 |
獎勵使用者
決定何時要獎勵使用者時,務必在使用者體驗和獎勵驗證之間取得平衡。伺服器端回呼可能會在觸及外部系統之前發生延遲。因此,建議的最佳做法是使用用戶端回呼立即獎勵使用者,並在收到伺服器端回呼時對所有獎勵進行驗證。這種做法可以提供良好的使用者體驗,同時確保授予的獎勵有效。
不過,如果應用程式必須確保獎勵有效性 (例如獎勵會影響應用程式遊戲內經濟),並且可以接受獎勵的延遲獎勵,那麼等待通過驗證的伺服器端回呼或許是最佳方式。
自訂資料
如果應用程式需要伺服器端驗證回呼中的額外資料,則應使用獎勵廣告的自訂資料功能。獎勵廣告物件上設定的任何字串值都會傳遞至 SSV 回呼的 custom_data
查詢參數。如未設定自訂資料值,SSV 回呼中就不會顯示 custom_data
查詢參數值。
以下程式碼範例示範如何在獎勵廣告載入後設定 SSV 選項。
void HandleRewardedAdLoaded(RewardedAd ad, AdFailedToLoadEventArgs error) { // Create and pass the SSV options to the rewarded ad. var options = new ServerSideVerificationOptions .Builder() .SetCustomData("SAMPLE_CUSTOM_DATA_STRING") .Build() ad.SetServerSideVerificationOptions(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 每隔一秒會再次嘗試傳送 SSV 回呼最多五次。 - 如何驗證 SSV 回呼是否來自 Google?
- 使用反向 DNS 查詢驗證 SSV 回呼是否來自 Google。