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

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

В этом руководстве показано, как проверить вознагражденные обратные вызовы SSV с помощью сторонней криптографической библиотеки Tink Java Apps , чтобы убедиться, что параметры запроса в обратном вызове являются допустимыми значениями. Хотя для целей данного руководства используется Tink, у вас есть возможность использовать любую стороннюю библиотеку, поддерживающую ECDSA . Вы также можете протестировать свой сервер с помощью инструмента тестирования в пользовательском интерфейсе AdMob.

Посмотрите этот полностью рабочий пример с использованием Java Spring-Boot.

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

Используйте RewardedAdsVerifier из библиотеки Java-приложений Tink.

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

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

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

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

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

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

1234567

Идентификаторы источников объявлений

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

Название источника объявлений Идентификатор источника объявления
Аарки (торг) 5240798063227064260
Генерация рекламы (торги) 1477265452970951479
Адколония 15586990674969969776
AdColony (без SDK) (назначение ставок) 4600416542059544716
AdColony (торги) 6895345910719072481
АдФалькон 3528208921554210682
Сеть Рекламы в приложении 5450213213286189855
Водопад сети AdMob 1215381445328257950
ADResult 10593873382626181482
АМоАд 17253994435944008978
Аппловин 1063618907739174004
Аппловин (торг) 1328079684332308356
Чартбуст 2873236629771172317
Шоколадная платформа (торги) 6432849193975106527
Межканальный (MdotM) 9372067028804390441
Пользовательское событие 18351550913290782395
Обмен ДТ*
* До 21 сентября 2022 года эта сеть называлась «Fyber Marketplace».
2179455223494392917
EMX (торги) 8497809869790333482
Флукт (торги) 8419777862490735710
шквал 3376427960656545613
Оптоволокно*
* Этот источник объявлений используется для исторических отчетов.
4839637394546996422
я-мобильный 5208827440166355534
Улучшение цифровых технологий (назначение ставок) 159382223051638006
Индексная биржа (торги) 4100650709078789802
ИнМоби 7681903010231960328
ИнМоби (торги) 6325663098072678541
Биржа InMobi (торги) 5264320421916134407
ЖелезоИсточник 6925240245545091930
Объявления IronSource (торги) 1643326773739866623
Свинцовый болт 2899150749497968595
LG U+AD 18298738678491729107
Рекламная сеть LINE 3025503711505004547
Майо 7505118203095108657
майо (торг) 1343336733822567166
Медиа.нет (торги) 2127936450554446159
Медированные собственные объявления 6060308706800320801
Сеть метааудиторий*
* До 6 июня 2022 г. эта сеть называлась «Facebook Audience Network».
10568273599589928883
Сеть мета-аудитории (торги)*
* До 6 июня 2022 года эта сеть называлась «Сеть аудитории Facebook (торги)».
11198165126854996598
Минтеграл 1357746574408896200
Минтеграл (торг) 6250601289653372374
МобФокс 8079529624516381459
МобФокс (торги) 3086513548163922365
MoPub ( устарело ) 10872986198578383917
моя цель 8450873672465271579
Ненд 9383070032774777750
Некссен (торг)*

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

2831998725945605450
ONE от AOL (Milleennial Media) 6101072188699264581
ONE от AOL (Nexage) 3224789793037044399
OneTag Exchange (торги) 4873891452523427499
OpenX (торги) 4918705482605678398
Пангл 4069896914521993236
Пангл (торг) 3525379893916449117
ПабМатик (торги) 3841544486172445473
Кампания по бронированию 7068401028668408324
RhythmOne (торги) 2831998725945605450
Рубикон (торг) 3993193775968767067
СК планета 734341340207269415
Долевое участие (торги) 5247944089976324188
Смаато (торг) 3362360112145450544
Экватив (торг)*

* До 12 января 2023 года эта сеть называлась «Смарт-адсервер».

5970199210771591442
Соноби (торг) 3270984106996027150
Тапджой 7295217276740746030
Тапджой (торги) 4692500501762622178
Тенсент ГДТ 7007906637038700218
TripleLift (торги) 8332676245392738510
Юнити-реклама 4970775877303683148
Unity Ads (торги) 7069338991535737586
Веризон Медиа 7360851262951344112
Verve Group (торг) 5013176581647059185
Впон 1940957084538325905
Стартовая монетизация*

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

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

* До 30 января 2023 года данная сеть называлась «Вунгл (торг)».

4692500501762622185
Доходность (торги) 4193081836471107579
YieldOne (торги) 3154533971590234104
Цукс 5506531810221735863

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

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

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

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

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

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

Ява

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

Котлин

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

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

Ручная проверка награжденных 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.