Проверка на стороне сервера

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

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

Требования

Как использовать RewardedAdsVerifier из библиотеки Tink Java Apps

В репозитории GitHub Tink Java Apps есть вспомогательный класс 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 Идентификатор источника объявлений, из которого было получено это объявление. Названия источников объявлений, соответствующие значениям идентификаторов, перечислены в разделе Идентификаторы источников объявлений. 1953547073528090325
ad_unit Идентификатор рекламного блока AdMob, который использовался для запроса объявления с вознаграждением. 2747237135
custom_data Специальная строка данных, предоставленная customData.

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

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

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

1234567

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

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

Название источника объявления Идентификатор источника объявлений
Ad Generation (назначение ставок)1477265452970951479
Сеть AdMob5450213213286189855
Каскад сети AdMob1215381445328257950
AppLovin1063618907739174004
AppLovin (назначение ставок)1328079684332308356
Bidease (назначение ставок)3670825090829827805
BidMachine (назначение ставок)7943972370566394673
Chartboost2873236629771172317
Chocolate Platform (назначение ставок)6432849193975106527
Пользовательское событие18351550913290782395
DT Exchange*

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

2179455223494392917
DT Exchange (назначение ставок)8189833498765234879
Equativ (назначение ставок)*

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

5970199210771591442
Fluct (назначение ставок)8419777862490735710
i-mobile5208827440166355534
Improve Digital (назначение ставок)159382223051638006
Index Exchange (назначение ставок)4100650709078789802
InMobi7681903010231960328
InMobi (SDK) (назначение ставок)8468954295581492586
InMobi Exchange (назначение ставок)5264320421916134407
ironSource Ads6925240245545091930
ironSource Ads (назначение ставок)1643326773739866623
Liftoff Monetize*

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

1953547073528090325
Liftoff Monetize (ставки)*

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

4692500501762622185
LY Ads Network3025503711505004547
LY Ads Network (назначение ставок)2615812619460460513
Magnite DV+ (назначение ставок)3993193775968767067
maio.7505118203095108657
Media.net (аукционы)2127936450554446159
Собственные объявления, агрегируемые с помощью медиации6060308706800320801
Meta Audience Network*

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

10568273599589928883
Meta Audience Network (назначение ставок)*

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

11198165126854996598
Mintegral1357746574408896200
Mintegral (назначение ставок)6250601289653372374
Mobfox (ставки)3086513548163922365
MobileFuse (назначение ставок)7303547408604090310
Moloco Ads SDK (назначение ставок)8267622065755668722
myTarget8450873672465271579
Nativo (назначение ставок)3240503836211327896
Nexxen (ставки)*

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

2831998725945605450
OneTag Exchange (назначение ставок)4873891452523427499
OpenX (ставки)4918705482605678398
Pangle4069896914521993236
Pangle KR SDK (назначение ставок)12171279046073404914
Pangle ROW SDK (назначение ставок)3525379893916449117
Pangle US SDK (назначение ставок)15999446638585856012
PubMatic (назначение ставок)3841544486172445473
PubMatic OpenWrap SDK7702975372504485373
PubMatic OpenWrap SDK (назначение ставок)1234567890123456789
Кампания с резервированием7068401028668408324
Повышение ставок6816468518946650043
Sharethrough (назначение ставок)5247944089976324188
Smaato (назначение ставок)3362360112145450544
Sonobi (назначение ставок)3270984106996027150
TripleLift (назначение ставок)8332676245392738510
Unity Ads4970775877303683148
Unity Ads (назначение ставок)7069338991535737586
Verve Group (назначение ставок)5013176581647059185
Vpon1940957084538325905
Yieldmo (назначение ставок)4193081836471107579
YieldOne (ставки)3154533971590234104
Zucks5506531810221735863

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

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

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

Специальные данные

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

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

RewardedAd.load(
  adUnitId: "_adUnitId",
  request: AdRequest(),
  rewardedAdLoadCallback: RewardedAdLoadCallback(
    onAdLoaded: (ad) {
      ServerSideVerificationOptions _options =
          ServerSideVerificationOptions(
            customData: 'SAMPLE_CUSTOM_DATA_STRING',
          );
      ad.setServerSideOptions(_options);
      _rewardedAd = ad;
    },
    onAdFailedToLoad: (error) {},
  ),
);

Замените SAMPLE_CUSTOM_DATA_STRING собственными данными.

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

Проверка 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 ожидает, что в ответ на обратные вызовы SSV будет возвращаться код статуса HTTP 200 OK. Если сервер недоступен или не предоставляет ожидаемый ответ, Google повторит попытку отправить обратные вызовы SSV до пяти раз с интервалом в одну секунду.
Как проверить, что обратные вызовы SSV приходят от Google?
Используйте обратный DNS-запрос, чтобы убедиться, что обратные вызовы SSV поступают от Google.