サーバー側の検証コールバックは、Google により展開されたクエリ パラメータを含む URL のリクエストです。このコールバックが外部システムに送信され、ユーザーにリワード広告またはリワード インタースティシャル広告の視聴に対する報酬を付与する必要があることが通知されます。リワード SSV(サーバーサイド認証)コールバックは、ユーザーに報酬を与えるクライアント側のコールバックのなりすましに対する追加の保護層となります。
このガイドでは、サードパーティの暗号化ライブラリ Tink Java Apps を使用して、リワード SSV コールバックを検証し、コールバックのクエリ パラメータが正当な値であることを確認する方法を説明します。このガイドでは Tink を使用していますが、ECDSA をサポートしているサードパーティのライブラリであればどれでも使用できます。また、AdMob 管理画面のテストツールでサーバーをテストすることもできます。
前提条件
- 広告ユニットでリワードのサーバー側の検証を有効にしていること
Tink Java Apps ライブラリの RewardedAdsVerifier を使用する
Tink Java Apps GitHub リポジトリには RewardedAdsVerifier
ヘルパークラスが含まれているため、リワード SSV コールバックの検証に必要なコードは少なくて済みます。このクラスを使用すると、次のコードでコールバック URL を検証できます。
RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
.fetchVerifyingPublicKeysWith(
RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
.build();
String rewardUrl = ...;
verifier.verify(rewardUrl);
verify()
メソッドが例外を発生せずに実行された場合、コールバック URL は正常に検証されています。ユーザーに報酬を与えるセクションには、報酬を付与するタイミングについてのおすすめの方法が記載されています。リワード SSV コールバックを検証するためにこのクラスで実行される各ステップについては、リワード SSV の手動検証セクションを参照してください。
SSV コールバック パラメータ
サーバー側の検証コールバックには、リワード広告のインタラクションを説明するクエリ パラメータが含まれています。パラメータ名、説明、値の例を以下に示します。パラメータはアルファベット順に送信されます。
パラメータ名 | 説明 | 値の例 |
---|---|---|
ad_network | この広告を配信した広告ソースの広告ソース ID。ID 値に対応する広告ソース名は、広告ソース ID セクションの一覧で確認できます。 | 1953547073528090325 |
ad_unit | リワード広告をリクエストするために使用された AdMob 広告ユニット ID。 | 2747237135 |
key_id | SSV コールバックを検証するために使用される鍵。この値は、AdMob 鍵サーバーによって提供される公開鍵にマッピングされます。 | 1234567890 |
reward_amount | 広告ユニットの設定で指定された報酬額。 | 5 |
reward_item | 広告ユニットの設定で指定された報酬アイテム。 | コイン |
signature | AdMob によって生成された SSV コールバックの署名。 | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
タイムスタンプ | ユーザが報酬を受けたときのエポック タイムスタンプ(ミリ秒単位)。 | 1507770365237823 |
transaction_id | AdMob によって生成された報酬付与イベントごとに固有の 16 進数でエンコードされた ID。 | 18fa792de1bca816048293fc71035638 |
user_id | SetUserId で指定されたユーザー ID。アプリでユーザー ID が指定されていない場合、このクエリ パラメータは SSV コールバックに存在しません。 |
1234567 |
広告ソース ID
広告ソースの名前と ID
広告ソース名 | 広告ソース ID |
---|---|
Aarki(入札) | 5240798063227064260 |
Ad Generation(入札) | 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 |
Custom Event | 18351550913290782395 |
DT Exchange* * 2022 年 9 月 21 日より前は、このネットワークは「Fyber Marketplace」と呼ばれていました。 | 2179455223494392917 |
EMX(入札) | 8497809869790333482 |
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 |
LG U+AD | 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 |
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 planet | 734341340207269415 |
Sharethrough(入札) | 5247944089976324188 |
Smaato(入札) | 3362360112145450544 |
Equativ(入札)* * 2023 年 1 月 12 日より前は、このネットワークは「Smart Adserver」と呼ばれていました。 | 5970199210771591442 |
Sonobi(入札) | 3270984106996027150 |
Tapjoy | 7295217276740746030 |
Tapjoy(入札) | 4692500501762622178 |
Tencent 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 の各種オプションを設定する方法を示したものです。
private void LoadRewardedAd(string adUnitId)
{
// Send the request to load the ad.
AdRequest adRequest = new AdRequest();
RewardedAd.Load(adUnitId, adRequest, (RewardedAd rewardedAd, LoadAdError error) =>
{
// If the operation failed with a reason.
if (error != null)
{
Debug.LogError("Rewarded ad failed to load an ad with error : " + error);
return;
}
var options = new ServerSideVerificationOptions
.Builder()
.SetCustomData("SAMPLE_CUSTOM_DATA_STRING")
.Build()
rewardedAd.SetServerSideVerificationOptions(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 コールバックの最後の 2 つのクエリ パラメータは、signature
と key_id,
で、常にこの順番となります。残りのクエリ パラメータは、検証されるコンテンツを指定します。リワード コールバックを https://www.myserver.com/mypath
に送信するように AdMob を設定するとしましょう。下のスニペットは、検証されるコンテンツがハイライト表示されたリワード 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 バイト配列としてコールバック URL から解析する方法を示しています。
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"));
コールバック URL から signature と key_id を取得する
前の手順の queryString
値を使用して、以下に示すように、コールバック URL の 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()));
検証を実行する
最後のステップは、適切な公開鍵でコールバック URL の内容を検証することです。parsePublicKeysJson
メソッドから返されたマッピングを取得し、コールバック URL の 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);
}
}
メソッドが例外を発生させずに実行された場合、コールバック URL は正常に検証されています。
よくある質問
- AdMob 鍵サーバーから提供された公開鍵をキャッシュできますか?
- SSV コールバックの検証に必要なオペレーションの数を減らすために、AdMob 鍵サーバーによって提供された公開鍵をキャッシュすることをおすすめします。ただし、公開鍵は定期的にローテーションされるため、24 時間以上キャッシュしないでください。
- AdMob 鍵サーバーによって提供される公開鍵はどのくらいの頻度でローテーションされますか?
- AdMob 鍵サーバーによって提供される公開鍵は、可変スケジュールでローテーションされます。SSV コールバックの検証が続けて行われるように、公開鍵は 24 時間以上キャッシュしないでください。
- サーバーにアクセスできない場合はどうなりますか?
- SSV コールバック用に
HTTP 200 OK
成功ステータス レスポンス コードが必要です。サーバーにアクセスできない場合または期待どおりのレスポンスが得られない場合、1 秒間隔で最大 5 回の SSV コールバックの送信が再試行されます。 - SSV コールバックが Google から送信されていることを確認するにはどうすればよいですか?
- SSV コールバックが Google から送信されていることを確認するには、リバース DNS ルックアップを使用します。