אימות קריאות חוזרות (callback) לצורך אימות בצד השרת

קריאות חוזרות לאימות בצד השרת הן בקשות לכתובות URL, עם פרמטרים של שאילתות שמורחבים על ידי Google, שנשלחות על ידי Google למערכת חיצונית כדי ליידע את המשתמש שצריך לקבל תגמול על אינטראקציה עם מודעת מעברון מתגמלת או מתגמלת. קריאות חוזרות (callback) מתגמלות (SSV) מספקות שכבת הגנה נוספת מפני זיוף של קריאות חוזרות (callback) בצד הלקוח, כדי לתגמל את המשתמשים.

במדריך הזה מוסבר איך לאמת קריאות חוזרות (callback) של צד שלישי מתגמלת באמצעות הספרייה הקריפטוגרפית של צד שלישי Tink Java Apps, כדי לוודא שהפרמטרים של השאילתה בקריאה החוזרת (callback) הם ערכים חוקיים. במדריך הזה משתמשים ב-Tink, אבל אפשר להשתמש בכל ספרייה של צד שלישי שתומכת ב-ECDSA. אתם יכולים לבדוק את השרת גם באמצעות כלי הבדיקה בממשק המשתמש של AdMob.

לפניכם דוגמה לדוגמה שפועלת באופן מלא באמצעות Java Spring-boot.

דרישות מוקדמות

שימוש ב-RewardAdsVerifier מספריית האפליקציות Tink Java

המאגר ב-GitHub של Tink Java Apps ב-GitHub כולל מחלקת עזרה של RewardedAdsVerifier, שמטרתה לצמצם את הקוד שנדרש כדי לאמת קריאה חוזרת (callback) מתגמלת. שימוש במחלקה הזו מאפשר לאמת כתובת URL לקריאה חוזרת (callback) באמצעות הקוד הבא.

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

אם השיטה verify() מופעלת בלי להעלות חריג, כתובת ה-URL לקריאה חוזרת (callback) אומתה בהצלחה. בקטע תגמול למשתמש מפורטות השיטות המומלצות לגבי מועדי התגמול של המשתמשים. בקטע אימות ידני של אימות ידני של מודעות בצד השרת המתגמלות מפורט פירוט של השלבים שהמחלקה הזו ביצעה כדי לאמת קריאה חוזרת (callback) מתגמלת.

פרמטרים של התקשרות חזרה בצד השרת (SSV)

קריאות חוזרות לאימות בצד השרת מכילות פרמטרים של שאילתה, שמתארים את האינטראקציה עם המודעה המתגמלת. שמות של פרמטרים, תיאורים וערכים לדוגמה מפורטים למטה. הפרמטרים נשלחים בסדר אלפביתי.

שם פרמטר התיאור ערך לדוגמה
ad_network מזהה של מקור המודעות של מקור המודעות שהפעיל את המודעה הזו. השמות של מקורות המודעות שתואמים לערכי המזהים מפורטים בקטע מזהים של מקורות מודעות. 1953547073528090325
ad_unit מזהה יחידת המודעות ב-AdMob ששימשה לבקשת המודעה המתגמלת. 2747237135
custom_data מחרוזת נתונים בהתאמה אישית כפי שסופקה על ידי customRewardString .

אם האפליקציה לא מספקת מחרוזת נתונים מותאמת אישית, הערך של פרמטר השאילתה הזה לא יוצג בקריאה החוזרת של ה-SSV.

SAMPLE_CUSTOM_DATA_STRING
key_id המפתח המשמש לאימות ההתקשרות החוזרת של ה-SSV. הערך הזה ממופה למפתח ציבורי שמסופק על ידי שרת המפתחות של AdMob. 1234567890
reward_amount סכום התגמול שצוין בהגדרות של יחידת המודעות. 5
reward_item פריט התגמול כפי שצוין בהגדרות של יחידת המודעות. מטבעות
signature חתימה לקריאה חוזרת (SSV) שנוצרה על ידי AdMob. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
חותמת זמן חותמת הזמן של המועד שבו המשתמש קיבל תגמול לפי זמן מערכת באלפיות השנייה. 1507770365237823
transaction_id מזהה ייחודי בקידוד הקסדצימלי לכל אירוע של הענקת תגמול שנוצר על ידי AdMob. 18fa792de1bca816048293fc71035638
user_id מזהה המשתמש כפי שסופק על ידי userIdentifier.

אם האפליקציה לא סיפקה מזהה משתמש, פרמטר השאילתה הזה לא יופיע בקריאה החוזרת של ה-SSV.

1234567

מזהים של מקורות מודעות

מזהים ושמות של מקורות מודעות

שם מקור המודעה מזהה של מקור מודעות
Aarki (בידינג)5240798063227064260
יצירת מודעות (בידינג)1477265452970951479
AdColony15586990674969969776
AdColony (לא SDK) (בידינג)4600416542059544716
AdColony (בידינג)6895345910719072481
AdFalcon3528208921554210682
רשת AdMob5450213213286189855
ADResult10593873382626181482
AMoAd17253994435944008978
אפלובין1063618907739174004
Appלובin (בידינג)1328079684332308356
Chartboost2873236629771172317
פלטפורמת שוקולד (בידינג)6432849193975106527
CrossChannel (MdotM)9372067028804390441
אירוע מותאם אישית18351550913290782395
DT Exchange*
* לפני 21 בספטמבר 2022, הרשת הזו נקראה 'Fyber Marketplace'.
2179455223494392917
EMX (בידינג)8497809869790333482
תנודות (בידינג)8419777862490735710
פלורי3376427960656545613
Fyber*
* מקור המודעות הזה משמש לדיווח היסטורי.
4839637394546996422
i-mobile5208827440166355534
שיפור הדיגיטל (בידינג)159382223051638006
המרת אינדקס (בידינג)4100650709078789802
InMobi7681903010231960328
InMobi (בידינג)6325663098072678541
IronSource6925240245545091930
Leadbolt2899150749497968595
LG U+AD18298738678491729107
רשת המודעות של LINE3025503711505004547
Maio7505118203095108657
maio (בידינג)1343336733822567166
Media.net (בידינג)2127936450554446159
מודעות הבית בתהליך בחירת הרשת6060308706800320801
Meta Audience Network*
* לפני 6 ביוני 2022, הרשת הזו נקראה "Facebook Audience Network".
10568273599589928883
Meta Audience Network (בידינג)*
* לפני 6 ביוני 2022, הרשת הזו נקראה 'Facebook Audience Network (בידינג)'
11198165126854996598
מינטגרל1357746574408896200
Mintegral (בידינג)6250601289653372374
MobFox8079529624516381459
MobFox (בידינג)3086513548163922365
MoPub (הוצאה משימוש)10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
ONE by AOL (Millennial Media)6101072188699264581
ONE by AOL (Nexage)3224789793037044399
OneTag Exchange (בידינג)4873891452523427499
OpenX (בידינג)4918705482605678398
Pangle (בידינג)3525379893916449117
PubMatic (בידינג)3841544486172445473
קמפיין בהזמנה7068401028668408324
RhythmOne (בידינג)2831998725945605450
Rubicon (בידינג)3993193775968767067
כוכב הלכת SK734341340207269415
שיעור המרות (Sharethrough) (בידינג)5247944089976324188
Smaato (בידינג)3362360112145450544
Equativ (בידינג)*

* לפני 12 בינואר 2023, הרשת הזו נקראה 'שרת מודעות חכם'.

5970199210771591442
Sonobi (בידינג)3270984106996027150
Tapjoy7295217276740746030
Tapjoy (בידינג)4692500501762622178
Tencent GDT7007906637038700218
TripleLift (בידינג)8332676245392738510
מודעות Unity4970775877303683148
UnrulyX (בידינג)2831998725945605450
Verizon Media7360851262951344112
קבוצת Verve (בידינג)5013176581647059185
VPN1940957084538325905
Liftoff Monetize*

* לפני 30 בינואר 2023, הרשת הזו נקראה Vungle.

1953547073528090325
Liftoff Monetize (בידינג)*

* לפני 30 בינואר 2023, הרשת הזו נקראה Vungle (בידינג).

4692500501762622185
Yieldmo (בידינג)4193081836471107579
YieldOne (בידינג)3154533971590234104
זאקס5506531810221735863

תגמול למשתמש

כשמחליטים מתי לתגמל משתמש, חשוב לאזן בין חוויית המשתמש לבין אימות התגמול. יכול להיות שיהיו עיכובים בהתקשרות חזרה בצד השרת לפני שהם יגיעו למערכות חיצוניות. לכן, השיטה המומלצת היא להשתמש בקריאה חוזרת (callback) בצד הלקוח כדי לתגמל את המשתמש באופן מיידי, תוך ביצוע אימות לכל התגמולים כשמקבלים קריאות חוזרות (callback) בצד השרת. הגישה הזו מספקת חוויית משתמש טובה וגם מבטיחה את תוקף התגמולים שניתנו.

עם זאת, במקרה של אפליקציות שבהן תוקף התגמול הוא קריטי (לדוגמה, התגמול משפיע על הכלכלה בתוך המשחק) ועיכובים במתן הפרסים הם מותרים, כדאי להמתין לקריאה החוזרת (callback) המאומתת בצד השרת.

נתונים בהתאמה אישית

באפליקציות שנדרשת עבורן נתונים נוספים בקריאות חוזרות לאימות בצד השרת, יש להשתמש בתכונה 'נתונים מותאמים אישית' במודעות המתגמלות. כל ערך מחרוזת שמוגדר באובייקט של מודעה מתגמלת מועבר לפרמטר השאילתה custom_data של הקריאה החוזרת (SSV). אם לא הוגדר ערך של נתונים מותאמים אישית, הערך של פרמטר השאילתה custom_data לא יופיע בקריאה החוזרת של ה-SSV.

דוגמת הקוד הבאה מדגימה איך להגדיר את אפשרויות ה-SSV אחרי טעינת המודעה המתגמלת.

Swift

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
    }

Objective-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;
              }];

אימות ידני של אימות בצד השרת של מודעות מתגמלות

השלבים שהמחלקה RewardedAdsVerifier ביצעה כדי לאמת אימות בצד השרת של מודעות מתגמלות מפורטים בהמשך. למרות שקטעי הקוד הכלולים נמצאים ב-Java ומשתמשים בספריית Tink של צד שלישי, תוכלו להטמיע את השלבים האלה בשפה שתבחרו באמצעות כל ספרייה של צד שלישי שתומכת ב-ECDSA.

אחזור מפתחות ציבוריים

כדי לאמת קריאה חוזרת (callback) של בצד השרת המתגמל, יש צורך במפתח ציבורי שיסופק על ידי AdMob.

משרת המפתחות של AdMob, אפשר לאחזר משרת המפתחות של AdMob רשימה של מפתחות ציבוריים שישמשו לאימות הקריאות החוזרות (callback) של ה-SSV המתגמל. רשימת המפתחות הציבוריים מסופקת כייצוג 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;
}

קבלת תוכן לאימות

שני הפרמטרים האחרונים של השאילתה בקריאות החוזרות (callback) של הצד השלישי המתגמלות הם תמיד signature ו-key_id, בסדר הזה. שאר הפרמטרים של השאילתה מציינים את התוכן שיש לבדוק. נניח שהגדרתם את המערכת של AdMob לשלוח קריאות חוזרות לתגמול אל https://www.myserver.com/mypath. בקטע הקוד שבהמשך מוצגת דוגמה לקריאה חוזרת (callback) של בצד השרת המתגמל (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 של קריאה חוזרת (callback) כמערך בייטים מסוג 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 לקריאה חוזרת (callback), כפי שמוצג בהמשך:

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 לקריאה חוזרת (callback) כדי לקבל את המפתח הציבורי מהמיפוי הזה. לאחר מכן מאמתים את החתימה באמצעות המפתח הציבורי הזה. השלבים האלה מודגמים למטה בשיטה 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 לקריאה חוזרת (callback) אומתה בהצלחה.

שאלות נפוצות

האם אפשר לשמור במטמון את המפתח הציבורי שסופק על ידי שרת המפתחות של AdMob?
מומלץ לשמור במטמון את המפתח הציבורי שסופק על ידי שרת המפתחות של AdMob, כדי לצמצם את מספר הפעולות שנדרשות לאימות קריאה חוזרת (callback) של אימות בצד השרת. עם זאת, שימו לב שמפתחות ציבוריים עוברים רוטציה באופן קבוע ולא אמורים להישמר במטמון למשך יותר מ-24 שעות.
באיזו תדירות מתבצעת רוטציה של המפתחות הציבוריים שסופקו על ידי שרת המפתחות של AdMob?
מפתחות ציבוריים שמסופקים על ידי שרת המפתחות של AdMob עוברים רוטציה לפי לוח זמנים משתנה. כדי להבטיח שהאימות של קריאות חוזרות (callback) מסוג SSV ימשיך לפעול כמצופה, אין לשמור מפתחות ציבוריים במטמון למשך יותר מ-24 שעות.
מה קורה אם לא ניתן להתחבר לשרת שלי?
Google מצפה לקבל קוד תגובה של HTTP 200 OK לציון סטטוס הצלחה בתגובה לקריאה חוזרת (SSV). אם לא ניתן להשיג את השרת או לא מספק את התגובה הצפויה, Google תנסה שוב לשלוח קריאות חוזרות (callback) של SSV עד חמש פעמים במרווחים של שנייה אחת.
איך אפשר לוודא שהקריאות החוזרות של האימות בצד השרת מגיעות מ-Google?
השתמשו בחיפוש DNS הפוך כדי לוודא שהקריאות החוזרות (callback) מסוג SSV מקורן ב-Google.