Memvalidasi callback verifikasi sisi server (SSV)

Callback verifikasi sisi server adalah permintaan URL, dengan parameter kueri diperluas oleh Google, yang dikirim oleh Google ke sistem eksternal untuk memberi tahu bahwa pengguna harus diberikan reward karena telah berinteraksi dengan iklan interstisial reward. Callback SSV (verifikasi sisi server) reward memberikan lapisan perlindungan tambahan terhadap spoofing callback sisi klien untuk memberikan reward kepada pengguna.

Panduan ini menunjukkan cara memverifikasi callback SSV yang diberi reward menggunakan Tink Aplikasi Java pihak ketiga kriptografis untuk memastikan bahwa parameter kueri di callback nilai yang sah. Meskipun Tink digunakan untuk tujuan panduan ini, Anda memiliki opsi untuk menggunakan library pihak ketiga yang mendukung ECDSA. Anda juga dapat menguji server dengan pengujian di UI AdMob.

Lihat aplikasi berikut yang berfungsi sepenuhnya contoh menggunakan Spring-boot Java.

Prasyarat

Menggunakan RewardAdsVerifier dari library Aplikasi Java Tink

Repositori GitHub Tink Java Apps berisi RewardedAdsVerifier untuk mengurangi kode yang diperlukan untuk memverifikasi callback SSV yang diberi reward. Menggunakan class ini memungkinkan Anda memverifikasi URL callback dengan kode berikut.

RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
    .fetchVerifyingPublicKeysWith(
        RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
    .build();
String rewardUrl = ...;
verifier.verify(rewardUrl);

Jika metode verify() dieksekusi tanpa memunculkan pengecualian, callback URL berhasil diverifikasi. Memberikan reward kepada pengguna menjelaskan praktik terbaik terkait kapan pengguna harus diberi reward. Untuk perincian langkah-langkah yang dilakukan oleh class ini untuk memverifikasi callback SSV yang diberi reward, Anda dapat membaca Verifikasi manual iklan reward SSV.

Parameter callback SSV

Callback verifikasi sisi server berisi parameter kueri yang menjelaskan interaksi iklan reward. Nama, deskripsi, dan nilai contoh parameter yang tercantum di bawah ini. Parameter dikirim dalam urutan abjad.

Nama Parameter Deskripsi Nilai contoh
ad_network ID sumber iklan untuk sumber iklan yang memenuhi iklan ini. Sumber iklan yang sesuai dengan nilai ID tercantum di kolom ID sumber. 1953547073528090325
ad_unit ID unit iklan AdMob yang digunakan untuk meminta iklan reward. 2747237135
custom_data String data kustom seperti yang disediakan oleh setCustomData .

Jika tidak ada string data kustom yang disediakan oleh aplikasi, parameter kueri ini nilai tersebut tidak akan ada dalam callback SSV.

SAMPLE_CUSTOM_DATA_STRING
key_id Kunci yang akan digunakan untuk memverifikasi callback SSV. Nilai ini dipetakan ke kunci publik yang disediakan oleh server kunci AdMob. 1234567890
reward_amount Jumlah reward seperti yang ditetapkan dalam setelan unit iklan. 5
reward_item Item reward seperti yang ditentukan dalam setelan unit iklan. koin
tanda tangan Tanda tangan untuk callback SSV yang dibuat oleh AdMob. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
timestamp Stempel waktu saat pengguna mendapatkan reward sebagai waktu Epoch dalam milidetik. 1507770365237823
transaction_id ID unik yang dienkode dengan heksadesimal untuk setiap peristiwa pemberian reward yang dihasilkan oleh AdMob. 18fa792de1bca816048293fc71035638
user_id ID pengguna seperti yang disediakan oleh setUserId.

Jika tidak ada ID pengguna yang disediakan oleh aplikasi, parameter kueri ini tidak akan ada di callback SSV.

1234567

ID sumber iklan

Nama dan ID sumber iklan

广告来源名称 广告来源 ID
Aarki(出价)5240798063227064260
Ad Generation(出价)1477265452970951479
AdColony 15586990674969969776
AdColony(非 SDK)(出价)4600416542059544716
AdColony(出价)6895345910719072481
AdFalcon3528208921554210682
AdMob 广告联盟5450213213286189855
AdMob 广告联盟广告瀑布流1215381445328257950
ADResult10593873382626181482
AMoAd17253994435944008978
AppLovin1063618907739174004
AppLovin(出价)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform(出价)6432849193975106527
跨渠道 (MdotM)9372067028804390441
自定义事件18351550913290782395
DT Exchange*
* 在 2022 年 9 月 21 日之前,该广告联盟称为“Fyber Marketplace”。
2179455223494392917
EMX(出价)8497809869790333482
Fluct(出价)8419777862490735710
小风3376427960656545613
Fyber*
* 此广告来源用于生成历史报告。
4839637394546996422
i-mobile5208827440166355534
优化数字化(出价)159382223051638006
Index Exchange(出价)4100650709078789802
InMobi7681903010231960328
InMobi(出价)6325663098072678541
InMobi Exchange(出价)5264320421916134407
IronSource6925240245545091930
ironSource Ads(出价)1643326773739866623
Leadbolt2899150749497968595
LG U+AD18298738678491729107
LINE 广告联盟3025503711505004547
maio7505118203095108657
maio(出价)1343336733822567166
Media.net(出价)2127936450554446159
参与中介的自家广告6060308706800320801
Meta Audience Network*
* 在 2022 年 6 月 6 日之前,该广告联盟称为“Facebook Audience Network”。
10568273599589928883
Meta Audience Network(出价)*
* 在 2022 年 6 月 6 日之前,该广告联盟称为“Facebook Audience Network(出价)”。
11198165126854996598
Mintegral1357746574408896200
Mintegral(出价)6250601289653372374
MobFox8079529624516381459
MobFox(出价)3086513548163922365
MoPub(已弃用10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
Nexxen(出价)*

* 在 2024 年 5 月 1 日之前,该广告联盟称为“UnrulyX”。

2831998725945605450
ONE by AOL (Millennial Media)6101072188699264581
ONE by AOL (Nexage)3224789793037044399
OneTag Exchange(出价)4873891452523427499
OpenX(出价)4918705482605678398
邦格尔4069896914521993236
Pangle(出价)3525379893916449117
PubMatic(出价)3841544486172445473
预订型广告系列7068401028668408324
RhythmOne(出价)2831998725945605450
Rubicon(出价)3993193775968767067
SK 星球734341340207269415
Sharethrough(出价)5247944089976324188
Smaato(出价)3362360112145450544
Equativ(出价)*

* 在 2023 年 1 月 12 日之前,该广告联盟称为“Smart Adserver”。

5970199210771591442
Sonobi(出价)3270984106996027150
Tapjoy7295217276740746030
Tapjoy(出价)4692500501762622178
Tencent GDT7007906637038700218
TripleLift(出价)8332676245392738510
Unity 广告4970775877303683148
Unity Ads(出价)7069338991535737586
Verizon Media7360851262951344112
Verve Group(出价)5013176581647059185
Vpon1940957084538325905
Liftoff Monetize*

* 在 2023 年 1 月 30 日之前,该广告联盟称为“Vungle”。

1953547073528090325
Liftoff Monetize(出价)*

* 在 2023 年 1 月 30 日之前,该广告联盟称为“Vungle(出价)”。

4692500501762622185
Yieldmo(出价)4193081836471107579
YieldOne(出价)3154533971590234104
Zucks5506531810221735863

Memberikan reward kepada pengguna

Penting untuk menyeimbangkan pengalaman pengguna dan validasi penghargaan saat memutuskan kapan perlu memberikan reward kepada pengguna. Callback sisi server mungkin mengalami penundaan sebelum menjangkau sistem eksternal. Oleh karena itu, praktik terbaik yang direkomendasikan adalah menggunakan callback sisi klien untuk segera memberi reward kepada pengguna, saat melakukan dan validasi semua reward setelah menerima callback sisi server. Ini pendekatan ini memberikan pengalaman pengguna yang baik sambil memastikan validitas reward.

Namun, untuk aplikasi yang menekankan validitas reward (misalnya, reward memengaruhi ekonomi dalam game aplikasi) dan keterlambatan dalam memberikan reward masih dapat diterima, menunggu callback sisi server yang terverifikasi mungkin adalah yang terbaik pendekatan.

Data kustom

Aplikasi yang memerlukan data tambahan di callback verifikasi sisi server harus menggunakan fitur data kustom iklan reward. Nilai string apa pun yang ditetapkan pada iklan reward diteruskan ke parameter kueri custom_data dari callback SSV. Jika tidak nilai data kustom ditetapkan, nilai parameter kueri custom_data tidak akan ada di callback SSV.

Contoh kode berikut menunjukkan cara mengatur opsi SSV setelah iklan reward dimuat.

Java

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

Kotlin

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

Jika ingin menetapkan string reward kustom, Anda harus melakukannya sebelum menampilkan iklan.

Verifikasi manual SSV reward

Langkah-langkah yang dilakukan oleh class RewardedAdsVerifier untuk memverifikasi reward SSV diuraikan di bawah ini. Meskipun cuplikan kode yang disertakan ada di Java dan memanfaatkan library pihak ketiga Tink, langkah-langkah ini dapat Anda terapkan bahasa pilihan Anda, menggunakan library pihak ketiga yang mendukung ECDSA.

Mengambil kunci publik

Untuk memverifikasi callback SSV reward, Anda memerlukan kunci publik yang disediakan oleh AdMob.

Daftar kunci publik yang akan digunakan untuk memvalidasi callback SSV yang diberi reward dapat diambil dari kunci AdMob server. Daftar kunci publik disediakan sebagai representasi JSON dengan format yang mirip dengan berikut ini:

{
 "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=="
    },
  ],
}

Untuk mengambil kunci publik, hubungkan ke server kunci AdMob dan download tombol. Kode berikut akan menyelesaikan tugas ini dan menyimpan JSON representasi kunci untuk variabel 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();
}

Perlu diingat bahwa kunci publik dirotasi secara berkala. Anda akan menerima email untuk menginformasikan tentang rotasi yang akan datang. Jika Anda menyimpan kunci publik dalam cache, Anda harus memperbarui kunci setelah menerima email ini.

Setelah kunci publik diambil, kunci tersebut harus diurai. Tujuan Metode parsePublicKeysJson di bawah mengambil string JSON, seperti contoh di atas, sebagai input, dan membuat pemetaan dari nilai key_id ke kunci publik, yang dienkapsulasi sebagai objek ECPublicKey dari library 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;
}

Meminta verifikasi konten

Dua parameter kueri terakhir dari callback SSV reward selalu signature dan key_id, dalam urutan tersebut. Parameter kueri yang tersisa menentukan konten diverifikasi. Anggaplah Anda mengonfigurasi AdMob untuk mengirim callback reward ke https://www.myserver.com/mypath. Cuplikan di bawah menampilkan contoh iklan reward Callback SSV dengan konten yang akan diverifikasi ditandai.

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

Kode di bawah ini menunjukkan cara mengurai konten yang akan diverifikasi dari sebuah URL callback sebagai array byte 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"));

Mendapatkan signature dan key_id dari URL callback

Dengan menggunakan nilai queryString dari langkah sebelumnya, urai signature dan key_id parameter kueri dari URL callback seperti yang ditunjukkan di bawah ini:

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

Melakukan verifikasi

Langkah terakhir adalah memverifikasi konten URL callback dengan kunci publik yang sesuai. Ambil pemetaan yang dikembalikan dari Metode parsePublicKeysJson dan gunakan parameter key_id dari callback untuk mendapatkan kunci publik dari pemetaan tersebut. Kemudian memverifikasinya dengan kunci publik tersebut. Langkah-langkah ini ditunjukkan di bawah dalam metode 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);
  }
}

Jika metode dieksekusi tanpa menampilkan pengecualian, URL callback-nya adalah berhasil diverifikasi.

FAQ

Dapatkah saya menyimpan kunci publik yang disediakan oleh server kunci AdMob ke dalam cache?
Sebaiknya simpan kunci publik yang disediakan oleh kunci AdMob ke dalam cache server untuk mengurangi jumlah operasi yang diperlukan untuk memvalidasi SSV callback. Namun, perhatikan bahwa kunci publik dirotasi secara berkala dan tidak boleh disimpan di cache selama lebih dari 24 jam.
Seberapa sering kunci publik yang disediakan oleh server kunci AdMob dirotasi?
Kunci publik yang disediakan oleh server kunci AdMob dirotasi pada variabel jadwal proyek. Untuk memastikan bahwa verifikasi callback SSV terus berfungsi sebagai kunci publik tidak boleh di-cache lebih dari 24 jam.
Apa yang terjadi jika server saya tidak dapat dijangkau?
Google mengharapkan kode respons status sukses HTTP 200 OK untuk SSV callback. Jika server tidak dapat dijangkau atau tidak memberikan respons Google akan mencoba kembali mengirimkan panggilan balik SSV hingga lima kali dengan interval satu detik.
Bagaimana cara memverifikasi bahwa callback SSV berasal dari Google?
Gunakan pencarian balik DNS untuk memverifikasi bahwa callback SSV berasal dari Google.