Convalidare i callback della verifica lato server (SSV)

I callback di verifica lato server sono richieste URL, con parametri di ricerca espansi da Google, che vengono inviati da Google a un sistema esterno per comunicare che un utente deve ricevere una ricompensa per l'interazione con un annuncio interstitial con premio o con premio. I callback SSV (verifica lato server) con premio forniscono un ulteriore livello di protezione contro lo spoofing dei callback lato client per premiare gli utenti.

Questa guida mostra come verificare i callback SSV con premio utilizzando la libreria crittografica di terze parti Tink Java Apps per assicurarsi che i parametri di query nel callback siano valori legittimi. Sebbene Tink venga utilizzato ai fini di questa guida, hai la possibilità di utilizzare qualsiasi libreria di terze parti che supporti ECDSA. Puoi anche testare il server con lo strumento di test nell'interfaccia utente di AdMob.

Dai un'occhiata all'esempio di SSV con premio utilizzando Java Spring Boot.

Prerequisiti

Utilizza RewardedAdsVerifier dalla libreria Tink Java Apps

Il repository GitHub Tink Java Apps include una classe helper RewardedAdsVerifier per ridurre il codice necessario per verificare un callback SSV con premio. L'utilizzo di questa classe ti consente di verificare un URL di callback con il seguente codice.

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

Se il metodo verify() viene eseguito senza generare un'eccezione, l'URL di callback è stato verificato correttamente. La sezione Premi l'utente descrive in dettaglio le best practice relative al momento in cui gli utenti devono essere premiati. Per una suddivisione dei passaggi eseguiti da questa classe per verificare i callback SSV con premio, puoi leggere la sezione Verifica manuale di SSV con premio.

Parametri di callback SSV

I callback di verifica lato server contengono parametri di ricerca che descrivono l'interazione con l'annuncio con premio. Di seguito sono riportati i nomi, le descrizioni e i valori di esempio dei parametri. I parametri vengono inviati in ordine alfabetico.

Nome parametro Descrizione Valore di esempio
ad_network Identificatore dell'origine annuncio che ha soddisfatto questo annuncio. I nomi delle origini annuncio corrispondenti ai valori ID sono elencati nella sezione Identificatori delle origini annuncio. 1953547073528090325
ad_unit L'ID unità pubblicitaria AdMob utilizzato per richiedere l'annuncio con premio. 2747237135
custom_data Stringa di dati personalizzati fornita da setCustomData.

Se l'app non fornisce alcuna stringa di dati personalizzati, questo valore del parametro di query non sarà presente nel callback SSV.

SAMPLE_CUSTOM_DATA_STRING
key_id Chiave da utilizzare per verificare il callback SSV. Questo valore corrisponde a una chiave pubblica fornita dal server delle chiavi di AdMob. 1234567890
reward_amount Importo del premio specificato nelle impostazioni dell'unità pubblicitaria. 5
reward_item L'elemento premio specificato nelle impostazioni dell'unità pubblicitaria. monete
firma Firma per il callback SSV generata da AdMob. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
timestamp Timestamp di quando l'utente è stato premiato come ora Epoch in ms. 1507770365237823
transaction_id Identificatore univoco codificato in esadecimale per ogni evento di concessione di premi generato da AdMob. 18fa792de1bca816048293fc71035638
user_id Identificatore utente fornito da setUserId.

Se l'app non fornisce alcun identificatore utente, questo parametro di query non sarà presente nel callback SSV.

1234567

Identificatori delle origini annuncio

Nomi e ID delle origini annuncio

Nome origine annuncio ID origine annuncio
Ad Generation (offerte)1477265452970951479
AdColony15586990674969969776
AdColony (asta)6895345910719072481
AdFalcon3528208921554210682
Rete AdMob5450213213286189855
Struttura a cascata della Rete AdMob1215381445328257950
AppLovin1063618907739174004
AppLovin (asta)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform (offerte)6432849193975106527
Evento personalizzato18351550913290782395
DT Exchange*
* Prima del 21 settembre 2022, questa rete si chiamava "Fyber Marketplace".
2179455223494392917
Equativ (bidding)*

* Prima del 12 gennaio 2023, questa rete si chiamava "Smart Adserver".

5970199210771591442
Fluct (offerte)8419777862490735710
Flurry3376427960656545613
Fyber*
* Questa origine annuncio viene utilizzata per i report storici.
4839637394546996422
i-mobile5208827440166355534
Improve Digital (offerte)159382223051638006
Index Exchange (offerte)4100650709078789802
InMobi7681903010231960328
InMobi (asta)6325663098072678541
InMobi Exchange (asta)5264320421916134407
IronSource6925240245545091930
ironSource Ads (offerta)1643326773739866623
Leadbolt2899150749497968595
Liftoff Monetize*

* Prima del 30 gennaio 2023, questa rete si chiamava "Vungle".

1953547073528090325
Liftoff Monetize (bidding)*

* Prima del 30 gennaio 2023, questa rete si chiamava "Vungle (offerta)".

4692500501762622185
LG U+AD18298738678491729107
LINE Ads Network3025503711505004547
Magnite DV+ (offerta)3993193775968767067
maio7505118203095108657
maio (offerte)1343336733822567166
Media.net (asta)2127936450554446159
Annunci autopromozionali mediati6060308706800320801
Meta Audience Network*
* Prima del 6 giugno 2022, questa rete si chiamava "Facebook Audience Network".
10568273599589928883
Meta Audience Network (offerta)*
* Prima del 6 giugno 2022, questa rete si chiamava "Facebook Audience Network (offerta)".
11198165126854996598
Mintegral1357746574408896200
Mintegral (offerte)6250601289653372374
MobFox (asta)3086513548163922365
MoPub (obsoleto)10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
Nexxen (offerte)*

* Prima del 1° maggio 2024, questa rete si chiamava "UnrulyX".

2831998725945605450
OneTag Exchange (offerte)4873891452523427499
OpenX (offerte)4918705482605678398
Pangle4069896914521993236
Pangle (offerte)3525379893916449117
PubMatic (offerta)3841544486172445473
Campagna basata su prenotazione7068401028668408324
SK planet734341340207269415
Sharethrough (offerte)5247944089976324188
Smaato (offerte)3362360112145450544
Sonobi (asta)3270984106996027150
Tapjoy7295217276740746030
Tapjoy (asta)4692500501762622178
Tencent GDT7007906637038700218
TripleLift (offerte)8332676245392738510
Unity Ads4970775877303683148
Unity Ads (asta)7069338991535737586
Verve Group (offerte)5013176581647059185
Vpon1940957084538325905
Yieldmo (asta)4193081836471107579
YieldOne (offerte)3154533971590234104
Zucks5506531810221735863

Premiare l'utente

È importante bilanciare l'esperienza utente e la convalida dei premi quando si decide quando premiare un utente. I callback lato server potrebbero subire ritardi prima di raggiungere i sistemi esterni. Pertanto, la best practice consigliata è utilizzare il callback lato client per premiare immediatamente l'utente, eseguendo la convalida di tutti i premi al ricevimento dei callback lato server. Questo approccio offre una buona esperienza utente, garantendo al contempo la validità dei premi assegnati.

Tuttavia, per le applicazioni in cui la validità del premio è fondamentale (ad esempio, il premio influisce sull'economia in-game dell'app) e i ritardi nella concessione dei premi sono accettabili, attendere il callback lato server verificato potrebbe essere l'approccio migliore.

Dati personalizzati

Le app che richiedono dati aggiuntivi nei callback di verifica lato server devono utilizzare la funzionalità di dati personalizzati degli annunci con premio. Qualsiasi valore stringa impostato su un oggetto annuncio con premio viene passato al parametro di query custom_data del callback SSV. Se non è impostato alcun valore di dati personalizzati, il valore del parametro query custom_data non sarà presente nel callback SSV.

L'esempio seguente imposta le opzioni SSV dopo il caricamento dell'annuncio con premio:

Java

RewardedAd.load(MainActivity.this, "AD_UNIT_ID",
    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, "AD_UNIT_ID",
    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
  }
})

Se vuoi impostare la stringa del premio personalizzato, devi farlo prima di mostrare l'annuncio.

Verifica manuale della verifica lato server con premio

Di seguito sono descritti i passaggi eseguiti dalla classe RewardedAdsVerifier per verificare un SSV con premio. Sebbene gli snippet di codice inclusi siano in Java e sfruttino la libreria di terze parti Tink, questi passaggi possono essere implementati nel linguaggio che preferisci utilizzando qualsiasi libreria di terze parti che supporti ECDSA.

Recupera chiavi pubbliche

Per verificare un callback SSV con premio, devi disporre di una chiave pubblica fornita da AdMob.

Un elenco di chiavi pubbliche da utilizzare per convalidare i callback SSV con premio può essere recuperato dal server delle chiavi AdMob. L'elenco delle chiavi pubbliche viene fornito come rappresentazione JSON con un formato simile al seguente:

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

Per recuperare le chiavi pubbliche, connettiti al server delle chiavi di AdMob e scarica le chiavi. Il seguente codice esegue questa attività e salva la rappresentazione JSON delle chiavi nella variabile 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();
}

Tieni presente che le chiavi pubbliche vengono ruotate regolarmente. Riceverai un'email per informarti di una rotazione imminente. Se memorizzi nella cache le chiavi pubbliche, devi aggiornarle alla ricezione di questa email.

Una volta recuperate, le chiavi pubbliche devono essere analizzate. Il metodo parsePublicKeysJson riportato di seguito accetta come input una stringa JSON, come l'esempio precedente, e crea un mapping dai valori key_id alle chiavi pubbliche, che vengono incapsulate come oggetti ECPublicKey della libreria 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;
}

Ottenere contenuti da verificare

Gli ultimi due parametri di query dei callback di verifica lato server con premio sono sempre signature e key_id, in questo ordine. I parametri di query rimanenti specificano i contenuti da verificare. Supponiamo di aver configurato AdMob per inviare callback premio a https://www.myserver.com/mypath. Lo snippet seguente mostra un esempio di callback SSV con i contenuti da verificare evidenziati.

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

Il codice seguente mostra come analizzare i contenuti da verificare da un URL di callback come array di 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"));

Recuperare la firma e key_id dall'URL di callback

Utilizzando il valore queryString del passaggio precedente, analizza i parametri di ricerca signature e key_id dall'URL di callback come mostrato di seguito:

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

Esegui la verifica

Il passaggio finale consiste nel verificare il contenuto dell'URL di callback con la chiave pubblica appropriata. Prendi la mappatura restituita dal metodo parsePublicKeysJson e utilizza il parametro key_id dell'URL di callback per ottenere la chiave pubblica da questa mappatura. Quindi, verifica la firma con questa chiave pubblica. Questi passaggi sono illustrati di seguito nel metodo 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);
  }
}

Se il metodo viene eseguito senza generare un'eccezione, l'URL di callback è stato verificato correttamente.

Domande frequenti

Posso memorizzare nella cache la chiave pubblica fornita dal server delle chiavi AdMob?
Ti consigliamo di memorizzare nella cache la chiave pubblica fornita dal server delle chiavi AdMob per ridurre il numero di operazioni necessarie per convalidare i callback SSV. Tuttavia, tieni presente che le chiavi pubbliche vengono ruotate regolarmente e non devono essere memorizzate nella cache per più di 24 ore.
Con quale frequenza vengono ruotate le chiavi pubbliche fornite dal server delle chiavi AdMob?
Le chiavi pubbliche fornite dal server delle chiavi AdMob vengono ruotate in base a una pianificazione variabile. Per garantire che la verifica dei callback SSV continui a funzionare come previsto, le chiavi pubbliche non devono essere memorizzate nella cache per più di 24 ore.
Che cosa succede se il mio server non è raggiungibile?
Google prevede un codice di risposta di stato di operazione riuscita HTTP 200 OK per i callback di verifica lato server. Se il server non è raggiungibile o non fornisce la risposta prevista, Google tenterà di inviare nuovamente i callback di verifica lato server fino a cinque volte a intervalli di un secondo.
Come faccio a verificare che i callback SSV provengano da Google?
Utilizza la ricerca DNS inversa per verificare che i callback SSV provengano da Google.