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


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

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

Предпосылки

Используйте 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

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

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

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

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

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

1234567

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

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

Название источника рекламы Идентификатор источника рекламы
Генерация рекламы (торги) 1477265452970951479
AdColony 15586990674969969776
AdColony (торги) 6895345910719072481
AdFalcon 3528208921554210682
Сеть AdMob 5450213213286189855
Водопад сети AdMob 1215381445328257950
Аппловин 1063618907739174004
Applovin (торги) 1328079684332308356
Chartboost 2873236629771172317
Шоколадная платформа (торги) 6432849193975106527
Пользовательское событие 18351550913290782395
Обмен DT*
* До 21 сентября 2022 года эта сеть называлась «Fyber Marketplace».
2179455223494392917
Equativ (торги)*

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

5970199210771591442
Fluct (торги) 8419777862490735710
Шквал 3376427960656545613
Файбер*
* Этот источник рекламы используется для исторических отчетов.
4839637394546996422
i-mobile 5208827440166355534
Улучшение цифровых технологий (торги) 159382223051638006
Индексная биржа (торги) 4100650709078789802
InMobi 7681903010231960328
InMobi (торги) 6325663098072678541
InMobi Exchange (торги) 5264320421916134407
Источник железа 6925240245545091930
ironSource Ads (торги) 1643326773739866623
свинцовый болт 2899150749497968595
Liftoff Monetize*

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

1953547073528090325
Liftoff Monetize (торги)*

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

4692500501762622185
LG U+AD 18298738678491729107
Рекламная сеть LINE 3025503711505004547
Magnite DV+ (торги) 3993193775968767067
майо 7505118203095108657
майо (торги) 1343336733822567166
Media.net (торги) 2127936450554446159
Посреднические домашние объявления 6060308706800320801
Сеть метааудитории*
* До 6 июня 2022 года эта сеть называлась «Facebook Audience Network».
10568273599589928883
Сеть метааудитории (торги)*
* До 6 июня 2022 года эта сеть называлась «Facebook Audience Network (bidding)».
11198165126854996598
Минтеграл 1357746574408896200
Mintegral (торги) 6250601289653372374
MobFox (торги) 3086513548163922365
MoPub ( устарело ) 10872986198578383917
мояЦель 8450873672465271579
Ненд 9383070032774777750
Nexxen (торги)*

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

2831998725945605450
OneTag Exchange (торги) 4873891452523427499
OpenX (торги) 4918705482605678398
Пэнгл 4069896914521993236
Пэнгл (торги) 3525379893916449117
PubMatic (торги) 3841544486172445473
Кампания по бронированию 7068401028668408324
СК планета 734341340207269415
Sharethrough (торги) 5247944089976324188
Smaato (торги) 3362360112145450544
Соноби (торги) 3270984106996027150
Тапджой 7295217276740746030
Tapjoy (торги) 4692500501762622178
Tencent GDT 7007906637038700218
TripleLift (торги) 8332676245392738510
Реклама Unity 4970775877303683148
Unity Ads (торги) 7069338991535737586
Verve Group (торги) 5013176581647059185
Впон 1940957084538325905
Yieldmo (торги) 4193081836471107579
YieldOne (торги) 3154533971590234104
Цукс 5506531810221735863

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

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

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

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

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

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

  firebase::gma::RewardedAd* rewarded_ad;
  rewarded_ad = new firebase::gma::RewardedAd();

  firebase::gma::RewardedAd::ServerSideVerificationOptions options;
  options.custom_data = "SAMPLE_CUSTOM_DATA_STRING";
  rewarded_ad->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 с открытыми ключами, которые инкапсулируются в объекты 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.