Проверка обратных вызовов проверки на стороне сервера (SSV)

Выберите платформу: Android iOS Unity Flutter Android (Legacy)

Server-side verification callbacks are URL requests, with query parameters expanded by Google, that are sent by Google to an external system to notify it that a user should be rewarded for interacting with a rewarded or rewarded interstitial ad. Rewarded SSV (server-side verification) callbacks provide an extra layer of protection against spoofing of client-side callbacks to reward users.

This guide shows you how to verify rewarded SSV callbacks by using the Tink Java Apps third-party cryptographic library to ensure that the query parameters in the callback are legitimate values. Although Tink is used for the purposes of this guide, you have the option to use any third-party library that supports ECDSA . You can also test your server with the testing tool in the AdMob UI.

Ознакомьтесь с примером Rewarded SSV, использующим Java Spring Boot.

Предварительные требования

Используйте RewardedAdsVerifier из библиотеки Tink Java Apps.

В репозитории 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

Обратные вызовы проверки на стороне сервера содержат параметры запроса, описывающие взаимодействие с рекламным объявлением, за которое начисляется вознаграждение. Названия параметров, их описания и примеры значений приведены ниже. Параметры передаются в алфавитном порядке.

Имя параметра Описание Пример значения
рекламная сеть Идентификатор источника объявления, который выполнил показ данного объявления. Названия источников объявления, соответствующие значениям ID, перечислены в разделе « Идентификаторы источников объявления» . 1953547073528090325
рекламный блок Идентификатор рекламного блока AdMob, использованный для запроса рекламного объявления с вознаграждением. 2747237135
пользовательские_данные Пользовательская строка данных, предоставляемая функцией setCustomData .

Если приложение не предоставит пользовательскую строку данных, значение этого параметра запроса не будет присутствовать в функции обратного вызова SSV.

SAMPLE_CUSTOM_DATA_STRING
key_id Ключ, используемый для проверки обратного вызова SSV. Это значение соответствует открытому ключу, предоставленному сервером ключей AdMob . 1234567890
сумма вознаграждения Размер вознаграждения указан в настройках рекламного блока. 5
награда_предмет Вознаграждение предоставляется в соответствии с настройками рекламного блока. монеты
подпись Подпись для функции обратного вызова SSV, сгенерированная AdMob. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
метка времени Отметка времени, когда пользователь получил вознаграждение, в формате Epoch time в миллисекундах. 1507770365237823
transaction_id Уникальный шестнадцатеричный идентификатор для каждого события предоставления вознаграждения, генерируемого AdMob. 18fa792de1bca816048293fc71035638
ID пользователя Идентификатор пользователя, предоставленный функцией setUserId .

Если приложение не предоставляет идентификатор пользователя, этот параметр запроса не будет присутствовать в функции обратного вызова SSV.

1234567

Идентификаторы источника рекламы

Названия и идентификаторы источников рекламы

Название источника рекламы Идентификатор источника рекламы
Генерация рекламы (ставки) 1477265452970951479
Сеть AdMob 5450213213286189855
Водопад сети AdMob 1215381445328257950
AppLovin 1063618907739174004
AppLovin (торги) 1328079684332308356
Предложение (торги) 3670825090829827805
BidMachine (система торгов) 7943972370566394673
Chartboost 2873236629771172317
Шоколадная платформа (торги) 6432849193975106527
Пользовательское событие 18351550913290782395
Обмен DT*

* До 21 сентября 2022 года эта сеть называлась "Fyber Marketplace".

2179455223494392917
DT Exchange (торги) 8189833498765234879
Экватив (торги)*

* До 12 января 2023 года эта сеть называлась "Smart Adserver".

5970199210771591442
Колебание (торги) 8419777862490735710
i-mobile 5208827440166355534
Улучшение цифровых технологий (торгов) 159382223051638006
Биржа индексов (торги) 4100650709078789802
InMobi 7681903010231960328
InMobi (SDK) (торги) 8468954295581492586
Биржа InMobi (торги) 5264320421916134407
ironSource Реклама 6925240245545091930
ironSource Реклама (торги) 1643326773739866623
Взлет Монетизировать*

* До 30 января 2023 года эта сеть называлась "Vungle".

1953547073528090325
Монетизация Liftoff (торги)*

* До 30 января 2023 года эта сеть называлась "Vungle (торги)".

4692500501762622185
LY Ads Network 3025503711505004547
Рекламная сеть LY (торги) 2615812619460460513
Magnite DV+ (торги) 3993193775968767067
майо 7505118203095108657
Media.net (торги) 2127936450554446159
Реклама домов, размещенная через посредников. 6060308706800320801
Мета-аудитория*

* До 6 июня 2022 года эта сеть называлась "Facebook Audience Network".

10568273599589928883
Meta Audience Network (торги)*

* До 6 июня 2022 года эта сеть называлась "Facebook Audience Network (bidding)".

11198165126854996598
Минтеграл 1357746574408896200
Минтеграл (торги) 6250601289653372374
Mobfox (торги) 3086513548163922365
MobileFuse (торги) 7303547408604090310
Moloco Ads SDK (система назначения ставок) 8267622065755668722
myTarget 8450873672465271579
Nativo (торги) 3240503836211327896
Нексен (торги)*

* До 1 мая 2024 года эта сеть называлась "UnrulyX".

2831998725945605450
OneTag Exchange (торги) 4873891452523427499
OpenX (торги) 4918705482605678398
Пэнгл 4069896914521993236
Pangle KR SDK (торги) 12171279046073404914
Pangle ROW SDK (торги) 3525379893916449117
Pangle US SDK (торги) 15999446638585856012
PubMatic (торги) 3841544486172445473
PubMatic OpenWrap SDK 7702975372504485373
PubMatic OpenWrap SDK (система торгов) 1234567890123456789
Кампания по бронированию 7068401028668408324
Повышение (ставки) 6816468518946650043
Совместное использование (торги) 5247944089976324188
Смаато (торги) 3362360112145450544
Соноби (торги) 3270984106996027150
TripleLift (торги) 8332676245392738510
Реклама Unity 4970775877303683148
Реклама в Unity (торги) 7069338991535737586
Verve Group (участник тендера) 5013176581647059185
Впон 1940957084538325905
Yieldmo (торги) 4193081836471107579
YieldOne (торги) 3154533971590234104
Цукс 5506531810221735863

Вознаграждение пользователя

It is important to balance user experience and reward validation when deciding when to reward a user. Server-side callbacks may experience delays before reaching external systems. Therefore, the recommended best practice is to use the client-side callback to reward the user immediately, while performing validation on all rewards upon the receipt of server-side callbacks. This approach provides a good user experience while ensuring the validity of granted rewards.

Однако для приложений, где достоверность вознаграждения имеет решающее значение (например, вознаграждение влияет на внутриигровую экономику вашего приложения) и задержки в начислении вознаграждений допустимы, ожидание подтвержденного обратного вызова на стороне сервера может быть наилучшим подходом.

Пользовательские данные

Приложениям, требующим дополнительных данных в коллбэках проверки на стороне сервера, следует использовать функцию пользовательских данных в объявлениях с вознаграждением. Любое строковое значение, заданное для объекта объявления с вознаграждением, передается в параметр запроса custom_data коллбэка SSV. Если значение пользовательских данных не задано, значение параметра запроса custom_data не будет присутствовать в коллбэке SSV.

В следующем примере параметры SSV устанавливаются после загрузки рекламного объявления с вознаграждением:

Java

RewardedAd.load(MainActivity.this, "AD_UNIT_ID",
    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;
  }
});

Котлин

RewardedAd.load(this, "AD_UNIT_ID",
    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
  }
})

Если вы хотите задать пользовательскую строку вознаграждения, это необходимо сделать до показа рекламы.

Ручная проверка вознагражденных 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 с открытыми ключами, которые инкапсулированы в объекты ECPublicKey из библиотеки Tink.

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
&timestamp=150777823&transaction_id=12...DEF&user_id=1234567&signature=ME...Z1c&key_id=1268887

Приведённый ниже код демонстрирует, как преобразовать содержимое, подлежащее проверке, из URL-адреса обратного вызова в массив байтов в кодировке 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 из URL-адреса обратного вызова.

Используя значение queryString из предыдущего шага, проанализируйте параметры запроса signature и key_id из URL-адреса обратного вызова, как показано ниже:

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);
  }
}

Если метод выполняется без генерации исключения, значит, URL-адрес обратного вызова был успешно проверен.

Часто задаваемые вопросы

Можно ли кэшировать открытый ключ, предоставленный сервером ключей AdMob?
Мы рекомендуем кэшировать открытый ключ, предоставленный сервером ключей AdMob, чтобы уменьшить количество операций, необходимых для проверки обратных вызовов SSV. Однако обратите внимание, что открытые ключи регулярно обновляются и не должны кэшироваться дольше 24 часов.
Как часто меняются открытые ключи, предоставляемые сервером ключей AdMob?
Открытые ключи, предоставляемые сервером ключей AdMob, обновляются по переменному расписанию. Для обеспечения корректной работы проверки обратных вызовов SSV открытые ключи не следует кэшировать дольше 24 часов.
Что произойдет, если мой сервер окажется недоступен?
Google ожидает код ответа HTTP 200 OK (успешное выполнение) для обратных вызовов SSV. Если ваш сервер недоступен или не предоставляет ожидаемый ответ, Google попытается отправить обратные вызовы SSV до пяти раз с интервалом в одну секунду.
Как я могу убедиться, что обратные вызовы SSV поступают от Google?
Используйте обратный DNS-запрос, чтобы убедиться, что обратные вызовы SSV исходят от Google.