サーバー側の検証(SSV)コールバックを検証する

サーバーサイドの確認コールバックは、クエリ パラメータを含む URL リクエスト Google によって拡張され、Google から外部システムに リワード広告やチャット メッセージなどの操作で報酬を リワード インタースティシャル広告。リワード SSV(サーバー側の検証)コールバック クライアントサイドのコールバックのなりすましに対する保護を強化します。 特典を提供しています

このガイドでは、 Tink Java アプリ(サードパーティ) この暗号ライブラリを使用して、コールバックのクエリ パラメータが確実に 判断します このガイドでは Tink を使用していますが、 サポートされているサードパーティ ライブラリを ECDSA。 また、testing ツールを使用します。

こちらの完全に機能している 例 使用します。

前提条件

Tink Java Apps ライブラリの RewardAdsVerifier を使用する

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 は正常に確認されました。ユーザーへの特典 では、ユーザーに報酬を付与するタイミングに関するベスト プラクティスを詳しく説明しています。1 つの リワード SSV コールバックを検証するためにこのクラスが実行するステップの詳細 詳しくは、Google 広告利用規約の SSV セクションがあります。

SSV コールバック パラメータ

サーバーサイドの検証コールバックには、 リワード広告のインタラクションです。パラメータ名、説明、値の例は、 下の一覧をご覧ください。パラメータはアルファベット順に送信されます。

パラメータ名 説明 値の例
ad_network この広告を配信した広告ソースの 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
タイムスタンプ ユーザーが報酬を受け取ったときのエポックタイムのタイムスタンプ(ミリ秒単位)。 1507770365237823
transaction_id AdMob によって生成された特典付与イベントごとに、16 進数でエンコードされた一意の ID。 18fa792de1bca816048293fc71035638
user_id 提供元: ユーザー ID userIdentifier

アプリからユーザー ID が提供されていない場合、このクエリ パラメータは SSV コールバックに含まれます。

1234567

広告ソースの識別子

広告ソースの名前と ID

広告ソース名 広告ソース ID
Aarki(入札)5240798063227064260
広告の生成(入札)1477265452970951479
AdColony15586990674969969776
AdColony(非 SDK)(入札)4600416542059544716
AdColony(入札)6895345910719072481
AdFalcon3528208921554210682
AdMob ネットワーク5450213213286189855
AdMob ネットワークのウォーターフォール1215381445328257950
ADResult10593873382626181482
AMoAd17253994435944008978
Applovin1063618907739174004
Applovin(入札)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform(入札)6432849193975106527
CrossChannel(MdotM)9372067028804390441
Custom Event18351550913290782395
DT Exchange*
* 2022 年 9 月 21 日より前までは、このネットワークは「Fyber Marketplace」と呼ばれていました。
2179455223494392917
EMX(入札)8497809869790333482
Fluct(入札)8419777862490735710
Flurry3376427960656545613
Fyber*
* この広告ソースは過去のレポートに使用されます。
4839637394546996422
i-mobile5208827440166355534
デジタルの改善(入札)159382223051638006
インデックス エクスチェンジ(入札)4100650709078789802
InMobi7681903010231960328
InMobi(入札)6325663098072678541
InMobi Exchange(入札)5264320421916134407
IronSource6925240245545091930
ironSource の広告(入札)1643326773739866623
Leadbolt2899150749497968595
LG U+AD18298738678491729107
LINE 広告ネットワーク3025503711505004547
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
Mintegral1357746574408896200
Mintegral(入札)6250601289653372374
MobFox8079529624516381459
MobFox(入札)3086513548163922365
MoPub(非推奨10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
Nexxen(入札)*

* 2024 年 5 月 1 日より前までは、このネットワークは「UnrulyX」と呼ばれていました。

2831998725945605450
ONE by AOL(Millennial Media)6101072188699264581
ONE by AOL(Nexage)3224789793037044399
OneTag Exchange(入札)4873891452523427499
OpenX(入札)4918705482605678398
パングル4069896914521993236
Pangle(入札)3525379893916449117
PubMatic(入札)3841544486172445473
純広告キャンペーン7068401028668408324
RhythmOne(入札)2831998725945605450
Rubicon(入札)3993193775968767067
SK 惑星734341340207269415
シェアスルー(入札)5247944089976324188
Smaato(入札)3362360112145450544
Equativ(入札)*

* 2023 年 1 月 12 日より前までは、このネットワークは「Smart Adserver」と呼ばれていました。

5970199210771591442
Sonobi(入札)3270984106996027150
Tapjoy7295217276740746030
Tapjoy(入札)4692500501762622178
テンセント GDT7007906637038700218
TripleLift(入札)8332676245392738510
Unity 広告4970775877303683148
Unity 広告(入札)7069338991535737586
グーグルメディア7360851262951344112
Verve グループ(入札)5013176581647059185
Vpon1940957084538325905
Liftoff Monetize*

* 2023 年 1 月 30 日までは、このネットワークは「Vungle」と呼ばれていました。

1953547073528090325
Liftoff Monetize(入札)*

* 2023 年 1 月 30 日までは、このネットワークは「Vungle(入札)」と呼ばれていました。

4692500501762622185
収益 mo(入札)4193081836471107579
RewardOne(入札)3154533971590234104
Zucks5506531810221735863

ユーザーへの特典

決定する際には、ユーザー エクスペリエンスと報酬検証のバランスを取ることが重要 ユーザーに報酬を 与えるタイミングを決定しますサーバーサイドのコールバックは、 外部システムに到達しますそのため、推奨されるベスト プラクティスは、 クライアントサイドのコールバックを実行して、ユーザーに サーバーサイド コールバックの受信時に、すべてのリワードの検証を行う。この ユーザー エクスペリエンスを損なわないように、Google Cloud で付与される 。

ただし、報酬の有効性が重要なアプリケーション( アプリのゲーム内経済に影響を及ぼし、報酬の付与が遅れた場合、 確認されたサーバーサイドのコールバックを待つことが、 アプローチです

カスタムデータ

サーバー側の検証コールバックで追加データを必要とするアプリでは、 リワード広告のカスタムデータ機能リワード広告に設定されている文字列値 SSV コールバックの custom_data クエリ パラメータに渡されます。「いいえ」の場合 カスタムデータ値が設定されている場合、custom_data クエリ パラメータ値は SSV コールバックに含まれます。

次のコードサンプルは、コマンドの後に SSV オプションを設定する方法を示しています。 リワード広告が読み込まれました。

Swift

GADRewardedAd.load(withAdUnitID:"ca-app-pub-3940256099942544/1712485313",
                       request: request,
                       completionHandler: { [self] ad, error in
      if let error != error {
      rewardedAd = ad
      let options = GADServerSideVerificationOptions()
      options.customRewardString = "SAMPLE_CUSTOM_DATA_STRING"
      rewardedAd.serverSideVerificationOptions = options
    }

Objective-C

GADRequest *request = [GADRequest request];
[GADRewardedAd loadWithAdUnitID:@"ca-app-pub-3940256099942544/1712485313"
                        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 と 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 鍵サーバーに接続して できます。次のコードはこのタスクを実行し、 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 メソッドは、 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。以下のスニペットは、リワード広告スペースで 検証するコンテンツがハイライト表示された 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"));

コールバック URL から署名と key_id を取得する

前の手順の queryString 値を使用して、signature を解析します。 次のように、コールバック URL から 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 メソッドを使用して、コールバックの key_id パラメータを使用します。 URL を使用して、そのマッピングから公開鍵を取得します。次に、署名を あります。これらのステップは、以下の 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 時間以上キャッシュしてはいけません。
サーバーにアクセスできない場合はどうなりますか?
SSV に対して HTTP 200 OK 成功ステータス レスポンス コードが返される 使用できます。サーバーにアクセスできない場合や、想定どおりの応答が得られない場合 リクエストが送信されると、Google は SSV コールバックの送信を 5 回まで再試行します。 1 秒間隔です。
SSV コールバックの送信元が Google であることを確認するにはどうすればよいですか?
リバース DNS ルックアップを使用して、SSV コールバックの送信元が Google であることを確認する。