ตรวจสอบโค้ดเรียกกลับการยืนยันฝั่งเซิร์ฟเวอร์ (SSV)

Callback ของการยืนยันฝั่งเซิร์ฟเวอร์คือคําขอ URL ที่มีพารามิเตอร์การค้นหา ขยายโดย Google ซึ่ง Google ส่งไปยังระบบภายนอกเพื่อ แจ้งให้ผู้ใช้ทราบว่าผู้ใช้ควรได้รับรางวัลจากการโต้ตอบกับสิ่งที่มีการให้รางวัล หรือ โฆษณาคั่นระหว่างหน้าที่มีการให้รางวัล Callback ของ SSV (การยืนยันฝั่งเซิร์ฟเวอร์) ที่มีการให้รางวัล ให้การป้องกันการปลอมแปลง Callback ฝั่งไคลเอ็นต์อีกชั้นหนึ่ง เพื่อมอบรางวัลแก่ผู้ใช้

คู่มือนี้แสดงวิธียืนยันการติดต่อกลับ SSV ที่มีการให้รางวัลโดยใช้ แอป Tink Java ของบุคคลที่สาม ไลบรารีการเข้ารหัสเพื่อให้แน่ใจว่าพารามิเตอร์การค้นหาใน Callback ค่าที่ถูกต้องตามกฎหมาย แม้ว่า Tink จะใช้ตามวัตถุประสงค์ของคู่มือนี้ แต่คุณมีตัวเลือกในการ ใช้ไลบรารีของบุคคลที่สามที่รองรับ ECDSA นอกจากนี้ คุณสามารถทดสอบเซิร์ฟเวอร์ของคุณด้วยการทดสอบ เครื่องมือใน UI ของ AdMob

ลองดูวิดีโอเกี่ยวกับ ตัวอย่าง โดยใช้ Java Spring-boot

ข้อกำหนดเบื้องต้น

ใช้ มีการให้รางวัลAdsVerifier จากไลบรารีของแอป Tink Java

ที่เก็บของ Tink Java Apps ใน GitHub มี RewardedAdsVerifier คลาส Helper เพื่อลดรหัสที่ต้องใช้ในการยืนยัน Callback SSV ที่มีการให้รางวัล การใช้คลาสนี้จะทำให้คุณสามารถยืนยัน URL เรียกกลับด้วยรหัสต่อไปนี้

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

หากเมธอด verify() ทำงานโดยไม่เพิ่มข้อยกเว้น Callback ยืนยัน URL สำเร็จแล้ว คอลัมน์การให้รางวัลแก่ผู้ใช้ ซึ่งจะอธิบายถึงแนวทางปฏิบัติแนะนำเกี่ยวกับเวลาที่ควรให้รางวัลแก่ผู้ใช้ สำหรับ รายละเอียดขั้นตอนที่คลาสนี้ดำเนินการเพื่อยืนยันการเรียกกลับ SSV ที่มีการให้รางวัล คุณสามารถอ่านการยืนยันโฆษณาที่มีการให้รางวัลด้วยตนเอง SSV

พารามิเตอร์ Callback ของ SSV

Callback ของการยืนยันฝั่งเซิร์ฟเวอร์มีพารามิเตอร์การค้นหาที่อธิบาย การโต้ตอบกับโฆษณาที่มีการให้รางวัล ชื่อพารามิเตอร์ คําอธิบาย และค่าของตัวอย่าง ที่ระบุไว้ด้านล่าง ระบบจะส่งพารามิเตอร์ตามลำดับตัวอักษร

ชื่อพารามิเตอร์ คำอธิบาย ค่าตัวอย่าง
ad_network ตัวระบุแหล่งที่มาของโฆษณาสําหรับแหล่งที่มาของโฆษณาที่ทำให้โฆษณานี้สมบูรณ์ แหล่งที่มาของโฆษณา ชื่อที่สอดคล้องกับค่ารหัสจะแสดงอยู่ในหน้าโฆษณา ตัวระบุแหล่งที่มา 1953547073528090325
ad_unit รหัสหน่วยโฆษณา AdMob ที่ใช้ในการขอโฆษณาที่มีการให้รางวัล 2747237135
key_id คีย์ที่จะใช้เพื่อยืนยัน Callback ของ SSV ค่านี้จะแมปกับคีย์สาธารณะ ที่ได้จากเซิร์ฟเวอร์คีย์ AdMob 1234567890
reward_amount จำนวนรางวัลตามที่ระบุไว้ในการตั้งค่าหน่วยโฆษณา 5
reward_item ให้รางวัลตามที่ระบุไว้ในการตั้งค่าหน่วยโฆษณา เหรียญ
ลายเซ็น ลายเซ็นสำหรับ Callback SSV ที่ AdMob สร้างขึ้น MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
การประทับเวลา การประทับเวลาที่ผู้ใช้ได้รับรางวัลเป็นเวลา Epoch ในหน่วยมิลลิวินาที 1507770365237823
transaction_id ตัวระบุที่ไม่ซ้ำกันแบบเลขฐาน 16 ที่เข้ารหัสสำหรับกิจกรรมการมอบรางวัลแต่ละกิจกรรมที่สร้างโดย AdMob 18fa792de1bca816048293fc71035638
user_id ตัวระบุผู้ใช้จาก SetUserId

หากแอปไม่ได้ให้ตัวระบุผู้ใช้ พารามิเตอร์การค้นหานี้จะไม่ อยู่ในการเรียกกลับของ SSV

1234567

ตัวระบุแหล่งที่มาของโฆษณา

ชื่อและรหัสแหล่งที่มาของโฆษณา

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
AdMob Network Waterfall1215381445328257950
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 Marketplace".
2179455223494392917
EMX (bidding)8497809869790333482
Fluct (bidding)8419777862490735710
Flurry3376427960656545613
Fyber*
* This ad source is used for historical reporting.
4839637394546996422
i-mobile5208827440166355534
Improve Digital (bidding)159382223051638006
Index Exchange (bidding)4100650709078789802
InMobi7681903010231960328
InMobi (bidding)6325663098072678541
InMobi Exchange (bidding)5264320421916134407
IronSource6925240245545091930
ironSource Ads (bidding)1643326773739866623
Leadbolt2899150749497968595
LG U+AD18298738678491729107
LINE Ads Network3025503711505004547
maio7505118203095108657
maio (bidding)1343336733822567166
Media.net (bidding)2127936450554446159
Mediated house ads6060308706800320801
Meta Audience Network*
* Prior to June 6, 2022, this network was called "Facebook Audience Network".
10568273599589928883
Meta Audience Network (bidding)*
* Prior to June 6, 2022, this network was called "Facebook Audience Network (bidding)".
11198165126854996598
Mintegral1357746574408896200
Mintegral (bidding)6250601289653372374
MobFox8079529624516381459
MobFox (bidding)3086513548163922365
MoPub (deprecated)10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
Nexxen (bidding)*

* Prior to May 1, 2024, this network was called "UnrulyX".

2831998725945605450
ONE by AOL (Millennial Media)6101072188699264581
ONE by AOL (Nexage)3224789793037044399
OneTag Exchange (bidding)4873891452523427499
OpenX (bidding)4918705482605678398
Pangle4069896914521993236
Pangle (bidding)3525379893916449117
PubMatic (bidding)3841544486172445473
Reservation campaign7068401028668408324
RhythmOne (bidding)2831998725945605450
Rubicon (bidding)3993193775968767067
SK planet734341340207269415
Sharethrough (bidding)5247944089976324188
Smaato (bidding)3362360112145450544
Equativ (bidding)*

* Prior to January 12, 2023, this network was called "Smart Adserver".

5970199210771591442
Sonobi (bidding)3270984106996027150
Tapjoy7295217276740746030
Tapjoy (bidding)4692500501762622178
Tencent GDT7007906637038700218
TripleLift (bidding)8332676245392738510
Unity Ads4970775877303683148
Unity Ads (bidding)7069338991535737586
Verizon Media7360851262951344112
Verve Group (bidding)5013176581647059185
Vpon1940957084538325905
Liftoff Monetize*

* Prior to January 30, 2023, this network was called "Vungle".

1953547073528090325
Liftoff Monetize (bidding)*

* Prior to January 30, 2023, this network was called "Vungle (bidding)".

4692500501762622185
Yieldmo (bidding)4193081836471107579
YieldOne (bidding)3154533971590234104
Zucks5506531810221735863

การให้รางวัลผู้ใช้

การสร้างสมดุลระหว่างประสบการณ์ของผู้ใช้และการยืนยันของรางวัลนั้นเป็นสิ่งสำคัญ เวลาที่จะให้รางวัลผู้ใช้ Callback ฝั่งเซิร์ฟเวอร์อาจล่าช้าก่อน ซึ่งเข้าถึงระบบภายนอกได้ ดังนั้น แนวทางปฏิบัติแนะนำคือให้ใช้ Callback ฝั่งไคลเอ็นต์เพื่อให้รางวัลผู้ใช้ทันที ขณะที่ดำเนินการ การตรวจสอบรางวัลทั้งหมดเมื่อได้รับ Callback ฝั่งเซิร์ฟเวอร์ ช่วงเวลานี้ จะให้ประสบการณ์ที่ดีแก่ผู้ใช้ ในขณะเดียวกันก็ตรวจสอบความถูกต้องของ รางวัล

อย่างไรก็ตาม สำหรับแอปพลิเคชันที่มีความจำเป็นต้องได้รับรางวัลสูง (ตัวอย่างเช่น รางวัลส่งผลต่อเศรษฐกิจในเกมของแอป) และความล่าช้าในการมอบรางวัล ยอมรับได้ การรอการยืนยันจากการเรียกกลับฝั่งเซิร์ฟเวอร์อาจดีที่สุด ของเรา

ข้อมูลที่กำหนดเอง

แอปที่ต้องใช้ข้อมูลเพิ่มเติมใน Callback สำหรับการยืนยันฝั่งเซิร์ฟเวอร์ควรใช้ ฟีเจอร์ข้อมูลที่กำหนดเองของโฆษณาที่มีการให้รางวัล ค่าสตริงใดๆ ที่กำหนดไว้ในโฆษณาที่มีการให้รางวัล ระบบจะส่งผ่านไปยังพารามิเตอร์การค้นหา custom_data ของ Callback SSV หากไม่ใช่ มีการตั้งค่าข้อมูลที่กำหนดเอง ค่าพารามิเตอร์การค้นหา custom_data จะไม่เพิ่มขึ้น อยู่ในการเรียกกลับของ SSV

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีตั้งค่าตัวเลือก SSV หลังจาก โหลดโฆษณาที่มีการให้รางวัลแล้ว

private void LoadRewardedAd(string adUnitId)
{
  // Send the request to load the ad.
  AdRequest adRequest = new AdRequest();
  RewardedAd.Load(adUnitId, adRequest, (RewardedAd rewardedAd, LoadAdError error) =>
  {
    // If the operation failed with a reason.
    if (error != null)
    {
        Debug.LogError("Rewarded ad failed to load an ad with error : " + error);
        return;
    }

    var options = new ServerSideVerificationOptions
                          .Builder()
                          .SetCustomData("SAMPLE_CUSTOM_DATA_STRING")
                          .Build()
    rewardedAd.SetServerSideVerificationOptions(options);
  });
}

หากต้องการตั้งค่าสตริงรางวัลที่กำหนดเอง คุณต้องดำเนินการก่อนที่จะแสดง โฆษณา

การยืนยัน SSV ที่มีการให้รางวัลด้วยตนเอง

ขั้นตอนที่ชั้นเรียน RewardedAdsVerifier ทำเพื่อยืนยันรางวัลที่ได้รับรางวัล SSV จะแสดงอยู่ด้านล่าง แม้ว่าข้อมูลโค้ดที่รวมไว้จะอยู่ใน Java และ ใช้ประโยชน์จากไลบรารี Tink ของบุคคลที่สาม คุณนำขั้นตอนเหล่านี้ไปใช้ได้ใน ภาษาที่คุณต้องการ โดยใช้ไลบรารีของบุคคลที่สามที่รองรับ ECDSA

ดึงข้อมูลคีย์สาธารณะ

หากต้องการยืนยัน Callback ของ SSV ที่มีการให้รางวัล คุณต้องมีคีย์สาธารณะจาก AdMob

รายการคีย์สาธารณะที่จะใช้เพื่อตรวจสอบ Callback 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;
}

รับเนื้อหาที่จะขอรับการยืนยัน

พารามิเตอร์การค้นหา 2 รายการสุดท้ายของ Callback SSV ที่มีการให้รางวัลจะเป็น signature เสมอ และ key_id, ตามลำดับนั้น พารามิเตอร์การค้นหาที่เหลือจะระบุเนื้อหา ต้องได้รับการยืนยัน สมมติว่าคุณกำหนดค่า AdMob ให้ส่ง Callback รางวัลไปยัง https://www.myserver.com/mypath ข้อมูลโค้ดด้านล่างแสดงตัวอย่างที่มีการให้รางวัล SSV Callback ซึ่งไฮไลต์เนื้อหาที่จะยืนยัน

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 เรียกกลับดังที่แสดงด้านล่าง

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 จาก Callback 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 Callback อย่างไรก็ตาม โปรดทราบว่าคีย์สาธารณะจะมีการหมุนเวียนเป็นประจำและไม่ควร ถูกแคชนานกว่า 24 ชั่วโมง
คีย์สาธารณะของเซิร์ฟเวอร์คีย์ AdMob มีการหมุนเวียนบ่อยเพียงใด
คีย์สาธารณะที่ได้รับจากเซิร์ฟเวอร์คีย์ AdMob จะหมุนเวียนไปกับตัวแปร กำหนดการ เพื่อให้แน่ใจว่าการยืนยัน Callback ของ SSV ยังคงทำงานตาม ควรแคชคีย์สาธารณะไว้นานกว่า 24 ชั่วโมง
จะเกิดอะไรขึ้นหากไม่สามารถเข้าถึงเซิร์ฟเวอร์ของฉัน
Google คาดว่าจะมีโค้ดตอบกลับสถานะความสำเร็จ HTTP 200 OK สำหรับ SSV Callback หากไม่สามารถเข้าถึงเซิร์ฟเวอร์ หรือไม่ได้ระบุ Google จะพยายามส่ง Callback ของ SSV อีกครั้งไม่เกิน 5 ครั้ง ทุก 1 วินาที
ฉันจะยืนยันได้อย่างไรว่าการเรียกกลับของ SSV มาจาก Google
ใช้การค้นหา DNS แบบย้อนกลับเพื่อยืนยันว่าการเรียกกลับ SSV มาจาก Google