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


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

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

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

ใช้ RewardedAdsVerifier จากไลบรารี Tink Java Apps

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

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

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

พารามิเตอร์การเรียกกลับ SSV

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

ชื่อพารามิเตอร์ คำอธิบาย ค่าตัวอย่าง
ad_network ตัวระบุแหล่งที่มาของโฆษณาสำหรับแหล่งที่มาของโฆษณาที่แสดงโฆษณานี้ ชื่อแหล่งที่มาของโฆษณา ที่สอดคล้องกับค่ารหัสจะแสดงอยู่ในส่วนตัวระบุแหล่งที่มาของโฆษณา 1953547073528090325
ad_unit รหัสหน่วยโฆษณา AdMob ที่ใช้ขอโฆษณาที่มีการให้รางวัล 2747237135
custom_data สตริงข้อมูลที่กำหนดเองตามที่ ServerSideVerificationOptions::custom_data ระบุ

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

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

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

1234567

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

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

ชื่อแหล่งที่มาของโฆษณา รหัสแหล่งที่มาของโฆษณา
การสร้างโฆษณา (การเสนอราคา)1477265452970951479
AdColony15586990674969969776
AdColony (การเสนอราคา)6895345910719072481
AdFalcon3528208921554210682
เครือข่าย AdMob5450213213286189855
การแสดงโฆษณาตามลำดับขั้นของเครือข่าย AdMob1215381445328257950
Applovin1063618907739174004
Applovin (การเสนอราคา)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform (การเสนอราคา)6432849193975106527
เหตุการณ์ที่กำหนดเอง18351550913290782395
DT Exchange*
* ก่อนวันที่ 21 กันยายน 2022 เครือข่ายนี้มีชื่อว่า "Fyber Marketplace"
2179455223494392917
Equativ (การเสนอราคา)*

* ก่อนวันที่ 12 มกราคม 2023 เครือข่ายนี้มีชื่อว่า "Smart Adserver"

5970199210771591442
Fluct (การเสนอราคา)8419777862490735710
Flurry3376427960656545613
Fyber*
* แหล่งที่มาของโฆษณานี้ใช้สําหรับการรายงานข้อมูลย้อนหลัง
4839637394546996422
i-mobile5208827440166355534
Improve Digital (การเสนอราคา)159382223051638006
Index Exchange (การเสนอราคา)4100650709078789802
InMobi7681903010231960328
InMobi (การเสนอราคา)6325663098072678541
InMobi Exchange (การเสนอราคา)5264320421916134407
IronSource6925240245545091930
ironSource Ads (การเสนอราคา)1643326773739866623
Leadbolt2899150749497968595
Liftoff Monetize*

* ก่อนวันที่ 30 มกราคม 2023 เครือข่ายนี้มีชื่อว่า "Vungle"

1953547073528090325
Liftoff Monetize (การเสนอราคา)*

* ก่อนวันที่ 30 มกราคม 2023 เครือข่ายนี้มีชื่อว่า "Vungle (การเสนอราคา)"

4692500501762622185
LG U+AD18298738678491729107
LINE Ads Network3025503711505004547
Magnite DV+ (การเสนอราคา)3993193775968767067
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
Mintegral1357746574408896200
Mintegral (การเสนอราคา)6250601289653372374
MobFox (การเสนอราคา)3086513548163922365
MoPub (เลิกใช้งานแล้ว)10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
Nexxen (การเสนอราคา)*

* ก่อนวันที่ 1 พฤษภาคม 2024 เครือข่ายนี้มีชื่อว่า "UnrulyX"

2831998725945605450
OneTag Exchange (การเสนอราคา)4873891452523427499
OpenX (การเสนอราคา)4918705482605678398
Pangle4069896914521993236
Pangle (การเสนอราคา)3525379893916449117
PubMatic (การเสนอราคา)3841544486172445473
แคมเปญแบบจองล่วงหน้า7068401028668408324
SK planet734341340207269415
Sharethrough (การเสนอราคา)5247944089976324188
Smaato (การเสนอราคา)3362360112145450544
Sonobi (การเสนอราคา)3270984106996027150
Tapjoy7295217276740746030
Tapjoy (การเสนอราคา)4692500501762622178
Tencent GDT7007906637038700218
TripleLift (การเสนอราคา)8332676245392738510
Unity Ads4970775877303683148
Unity Ads (การเสนอราคา)7069338991535737586
Verve Group (การเสนอราคา)5013176581647059185
Vpon1940957084538325905
Yieldmo (การเสนอราคา)4193081836471107579
YieldOne (การเสนอราคา)3154533971590234104
Zucks5506531810221735863

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

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

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

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

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

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีตั้งค่าข้อมูลที่กำหนดเองในออบเจ็กต์โฆษณาที่มีการให้รางวัล ก่อนที่จะขอโฆษณา

  firebase::gma::RewardedAd* rewarded_ad;
  rewarded_ad = new firebase::gma::RewardedAd();

  firebase::gma::RewardedAd::ServerSideVerificationOptions options;
  options.custom_data = "SAMPLE_CUSTOM_DATA_STRING";
  rewarded_ad->SetServerSideVerificationOptions(options);

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

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

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

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

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