Validasi panggilan balik verifikasi sisi server (SSV)

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

Callback verifikasi sisi server adalah permintaan URL, dengan parameter kueri yang diperluas oleh Google, yang dikirim oleh Google ke sistem eksternal untuk memberi tahu bahwa pengguna harus diberi reward karena berinteraksi dengan iklan interstisial reward atau reward. Callback SSV (verifikasi sisi server) yang diberi 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 library kriptografi pihak ketiga Tink untuk memastikan bahwa parameter kueri dalam callback adalah 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 alat pengujian di UI AdMob.

Lihat contoh yang sepenuhnya berfungsi ini menggunakan spring-boot Java.

Prasyarat

Menggunakan RewardedAdsVerifyer dari Tink

Repositori GitHub Tink menyertakan class helper RewardedAdsVerifier untuk mengurangi kode yang diperlukan untuk memverifikasi callback SSV yang diberi reward. Dengan menggunakan class ini bersama dengan library kriptografi pihak ketiga Tink, Anda dapat 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, URL callback akan berhasil diverifikasi. Bagian Memberikan reward kepada pengguna berisi detail praktik terbaik terkait kapan pengguna harus mendapatkan reward. Untuk perincian langkah-langkah yang dilakukan oleh class ini guna memverifikasi callback SSV reward, Anda dapat membaca bagian Verifikasi manual SSV reward.

Parameter callback SSV

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

Nama Parameter Deskripsi Nilai contoh
jaringan_iklan ID sumber iklan untuk sumber iklan yang memenuhi iklan ini. Nama sumber iklan yang sesuai dengan nilai ID tercantum di bagian ID sumber iklan. 1953547073528090325
unit_iklan ID unit iklan AdMob yang digunakan untuk meminta iklan reward. 2747237135
data_kustom String data kustom seperti yang disediakan oleh setCustomData .

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

CONTOH_JUDUL_DATA_KUSTOM
id_kunci 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] Jumlah reward seperti yang ditentukan di setelan unit iklan. 5
reward_item Item reward seperti yang ditentukan di setelan unit iklan. koin
tanda tangan Tanda tangan untuk callback SSV yang dihasilkan oleh AdMob. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
stempel waktu Stempel waktu saat pengguna menerima reward sebagai waktu Epoch dalam md. 1507770365237823
transaction_id ID unik heksadesimal yang dienkode untuk setiap peristiwa pemberian reward yang dihasilkan oleh AdMob. 18fa792de1bca816048293fc71035638
user_id ID pengguna seperti yang diberikan oleh setUserId.

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

1234567

ID sumber iklan

Nama dan ID sumber iklan

Ad source name Ad source ID
Aarki (bidding)5240798063227064260
Ad Generation (bidding)1477265452970951479
AdColony15586990674969969776
AdColony (non-SDK) (bidding)4600416542059544716
AdColony (bidding)6895345910719072481
AdFalcon3528208921554210682
AdMob Network5450213213286189855
ADResult10593873382626181482
AMoAd17253994435944008978
Applovin1063618907739174004
Applovin (bidding)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform (bidding)6432849193975106527
CrossChannel (MdotM)9372067028804390441
Custom Event18351550913290782395
DT Exchange
Prior to September 21, 2022, this network was called "Fyber".
4839637394546996422
Fluct (bidding)8419777862490735710
Flurry3376427960656545613
i-mobile5208827440166355534
Improve Digital (bidding)159382223051638006
Index Exchange (bidding)4100650709078789802
InMobi7681903010231960328
InMobi (bidding)6325663098072678541
IronSource6925240245545091930
Leadbolt2899150749497968595
LG U+AD18298738678491729107
maio7505118203095108657
maio (bidding)1343336733822567166
Media.net (bidding)2127936450554446159
Mediated house ads6060308706800320801
Meta Audience Network
Prior to June 6, 2022, this network was called the "Facebook Audience Network".
10568273599589928883
Meta Audience Network (bidding)
Prior to June 6, 2022, this network was called the "Facebook Audience Network (bidding)".
11198165126854996598
MobFox8079529624516381459
MoPub (deprecated)10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
ONE by AOL (Millennial Media)6101072188699264581
ONE by AOL (Nexage)3224789793037044399
OpenX (bidding)4918705482605678398
Pangle (bidding)3525379893916449117
PubMatic (bidding)3841544486172445473
Reservation campaign7068401028668408324
RhythmOne (bidding)2831998725945605450
Rubicon (bidding)3993193775968767067
SK planet734341340207269415
Smaato (bidding)3362360112145450544
Tapjoy7295217276740746030
Tapjoy (bidding)4692500501762622178
Tencent GDT7007906637038700218
TripleLift (bidding)8332676245392738510
Unity Ads4970775877303683148
Verizon Media7360851262951344112
Vpon1940957084538325905
Vungle1953547073528090325
Vungle (bidding)4692500501762622185
Zucks5506531810221735863

Memberikan reward kepada pengguna

Anda harus menyeimbangkan pengalaman pengguna dan validasi reward saat menentukan kapan harus memberikan reward kepada pengguna. Callback sisi server mungkin mengalami penundaan sebelum mencapai sistem eksternal. Oleh karena itu, praktik terbaik yang direkomendasikan adalah menggunakan callback sisi klien untuk segera memberikan reward kepada pengguna, sekaligus menjalankan validasi pada semua reward setelah penerimaan callback sisi server. Pendekatan ini memberikan pengalaman pengguna yang baik sekaligus memastikan validitas reward yang diberikan.

Namun, untuk aplikasi yang sangat mengutamakan validitas reward (misalnya, reward tersebut memengaruhi ekonomi dalam game aplikasi Anda) dan penundaan dalam memberikan reward dapat diterima, menunggu callback sisi server terverifikasi mungkin merupakan pendekatan terbaik.

Data kustom

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

Contoh kode berikut menunjukkan cara menetapkan 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 SSV yang diberi reward diuraikan di bawah. Meskipun cuplikan kode yang disertakan berada di Java dan memanfaatkan library pihak ketiga Tink, langkah-langkah ini dapat diterapkan oleh Anda dalam bahasa pilihan, 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 reward dapat diambil dari server kunci AdMob. Daftar kunci publik diberikan sebagai representasi JSON dengan format yang mirip dengan yang 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 kunci. Kode berikut menyelesaikan tugas ini dan menyimpan representasi JSON kunci tersebut ke 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();
}

Perhatikan bahwa kunci publik dirotasi secara teratur. Anda akan menerima email yang memberitahukan rotasi mendatang. Jika Anda meng-cache kunci publik, Anda harus mengupdate kunci setelah menerima email ini.

Setelah diambil, kunci publik harus diuraikan. Metode parsePublicKeysJson di bawah ini menggunakan 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;
}

Mendapatkan konten yang akan diverifikasi

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

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 menguraikan konten yang akan diverifikasi dari 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, uraikan parameter kueri signature dan key_id 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()));

Lakukan verifikasi

Langkah terakhir adalah memverifikasi konten URL callback dengan kunci publik yang sesuai. Ambil pemetaan yang ditampilkan dari metode parsePublicKeysJson dan gunakan parameter key_id dari URL callback untuk mendapatkan kunci publik dari pemetaan tersebut. Kemudian, verifikasi tanda tangan 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 memunculkan pengecualian, URL callback akan berhasil diverifikasi.

Pertanyaan Umum (FAQ)

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