Обратные вызовы проверки на стороне сервера — это запросы URL с расширенными Google параметрами запроса, которые Google отправляет во внешнюю систему, чтобы уведомить ее о том, что пользователь должен получить вознаграждение за взаимодействие с вознаграждением или промежуточным объявлением с вознаграждением. Вознаграждаемые обратные вызовы SSV (проверка на стороне сервера) обеспечивают дополнительный уровень защиты от подделки обратных вызовов на стороне клиента для вознаграждения пользователей.
В этом руководстве показано, как проверить обратные вызовы SSV с вознаграждением с помощью сторонней криптографической библиотеки Tink , чтобы убедиться, что параметры запроса в обратном вызове являются допустимыми значениями. Хотя для целей этого руководства используется Tink, у вас есть возможность использовать любую стороннюю библиотеку, поддерживающую ECDSA . Вы также можете протестировать свой сервер с помощью инструмента тестирования в пользовательском интерфейсе AdMob.
Посмотрите этот полностью рабочий пример с использованием Java spring-boot.
Предпосылки
Интегрируйте объявления с вознаграждением в свое мобильное приложение с помощьюверсии 7.28.0 или более поздней версии Google Mobile Ads SDK.
Включите проверку на стороне сервера с вознаграждением в своем рекламном блоке.
Используйте RewardedAdsVerifier от Tink
Репозиторий Tink GitHub включает вспомогательный класс RewardedAdsVerifier
для сокращения кода, необходимого для проверки обратного вызова SSV с вознаграждением. Использование этого класса вместе со сторонней криптографической библиотекой Tink позволяет проверить 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 |
custom_data | Пользовательская строка данных, предоставленная customRewardString .Если приложение не предоставляет пользовательскую строку данных, это значение параметра запроса не будет присутствовать в обратном вызове SSV. | SAMPLE_CUSTOM_DATA_STRING |
key_id | Ключ, который будет использоваться для проверки обратного вызова SSV. Это значение соответствует открытому ключу, предоставленному сервером ключей AdMob . | 1234567890 |
вознаграждение_сумма | Сумма вознаграждения, указанная в настройках рекламного блока. | 5 |
награда_предмет | Предмет вознаграждения, указанный в настройках рекламного блока. | монеты |
подпись | Подпись для обратного вызова SSV, созданная AdMob. | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
отметка времени | Отметка времени, когда пользователь был вознагражден, в виде времени эпохи в мс. | 1507770365237823 |
ID транзакции | Уникальный шестнадцатеричный идентификатор для каждого события предоставления вознаграждения, созданного AdMob. | 18fa792de1bca816048293fc71035638 |
ID пользователя | Идентификатор пользователя, предоставленныйuserIdentifier .Если приложение не предоставляет идентификатор пользователя, этот параметр запроса не будет присутствовать в обратном вызове SSV. | 1234567 |
Идентификаторы источника рекламы
Названия и идентификаторы источников рекламы
Название источника рекламы | Идентификатор источника рекламы |
---|---|
Аарки (торги) | 5240798063227064260 |
Генерация рекламы (торги) | 1477265452970951479 |
AdColony | 15586990674969969776 |
AdColony (не SDK) (торги) | 4600416542059544716 |
AdColony (торги) | 6895345910719072481 |
AdFalcon | 3528208921554210682 |
Сеть Рекламы в приложении | 5450213213286189855 |
ADРезультат | 10593873382626181482 |
AMoAd | 17253994435944008978 |
Аппловин | 1063618907739174004 |
Аппловин (торги) | 1328079684332308356 |
Чартбуст | 2873236629771172317 |
Шоколадная платформа (торги) | 6432849193975106527 |
Межканальный (MdotM) | 9372067028804390441 |
Пользовательское событие | 18351550913290782395 |
Обмен DT До 21 сентября 2022 года эта сеть называлась «Fyber». | 4839637394546996422 |
Флюкт (торги) | 8419777862490735710 |
Шквал | 3376427960656545613 |
я-мобиль | 5208827440166355534 |
Улучшение цифровых технологий (торги) | 159382223051638006 |
Биржа индексов (торги) | 4100650709078789802 |
ИнМоби | 7681903010231960328 |
ИнМоби (торги) | 6325663098072678541 |
источник железа | 6925240245545091930 |
Свинцовый болт | 2899150749497968595 |
LG U+AD | 18298738678491729107 |
майо | 7505118203095108657 |
майо (торги) | 1343336733822567166 |
Media.net (торги) | 2127936450554446159 |
Собственные объявления с посредником | 6060308706800320801 |
Сеть метааудитории До 6 июня 2022 года эта сеть называлась «Сеть аудитории Facebook». | 10568273599589928883 |
Сеть метааудитории (торги) До 6 июня 2022 года эта сеть называлась «Сеть аудитории Facebook (торги)». | 11198165126854996598 |
МобФокс | 8079529624516381459 |
MoPub ( устарело ) | 10872986198578383917 |
моя цель | 8450873672465271579 |
Ненд | 9383070032774777750 |
ONE от AOL (Millennial Media) | 6101072188699264581 |
ОДИН от AOL (Nexage) | 3224789793037044399 |
OpenX (торги) | 4918705482605678398 |
Пэнгл (торги) | 3525379893916449117 |
PubMatic (торги) | 3841544486172445473 |
Кампания резервирования | 7068401028668408324 |
RhythmOne (торги) | 2831998725945605450 |
Рубикон (торги) | 3993193775968767067 |
СК планета | 734341340207269415 |
Поделиться (торги) | 5247944089976324188 |
Смаато (торги) | 3362360112145450544 |
Эквивалент (торг)* * До 12 января 2023 г. эта сеть называлась «Умный рекламный сервер». | 5970199210771591442 |
Соноби (торги) | 3270984106996027150 |
Тапджой | 7295217276740746030 |
Tapjoy (торги) | 4692500501762622178 |
Тенсент ГДТ | 7007906637038700218 |
TripleLift (торги) | 8332676245392738510 |
Единая реклама | 4970775877303683148 |
UnrulyX (торги) | 2831998725945605450 |
Веризон Медиа | 7360851262951344112 |
Впон | 1940957084538325905 |
Старт монетизации* * До 30 января 2023 г. эта сеть называлась «Vungle». | 1953547073528090325 |
Подъем монетизации (торги)* * До 30 января 2023 г. эта сеть называлась «Vungle». | 4692500501762622185 |
Доходность (торги) | 4193081836471107579 |
YieldOne (торги) | 3154533971590234104 |
Цакс | 5506531810221735863 |
Вознаграждение пользователя
При принятии решения о вознаграждении пользователя важно сбалансировать пользовательский опыт и подтверждение вознаграждения. Обратные вызовы на стороне сервера могут иметь задержки перед достижением внешних систем. Поэтому рекомендуется использовать обратный вызов на стороне клиента для немедленного вознаграждения пользователя, выполняя при этом проверку всех вознаграждений после получения обратных вызовов на стороне сервера. Этот подход обеспечивает хороший пользовательский опыт, гарантируя действительность предоставленных вознаграждений.
Однако для приложений, где действительность вознаграждения имеет решающее значение (например, вознаграждение влияет на внутриигровую экономику вашего приложения) и допустимы задержки в предоставлении вознаграждений, ожидание подтвержденного обратного вызова на стороне сервера может быть лучшим подходом.
Пользовательские данные
Приложения, которым требуются дополнительные данные в обратных вызовах проверки на стороне сервера, должны использовать функцию пользовательских данных объявлений с вознаграждением. Любое строковое значение, заданное для объекта объявления с вознаграждением, передается в параметр запроса custom_data
обратного вызова SSV. Если значение настраиваемых данных не задано, значение параметра запроса custom_data
не будет присутствовать в обратном вызове SSV.
В следующем примере кода показано, как установить параметры SSV после загрузки объявления с вознаграждением.
Быстрый
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 }
Цель-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 и используют стороннюю библиотеку 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 ×tamp=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.