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

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

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

ดูตัวอย่างที่ทำงานได้อย่างเต็มรูปแบบนี้โดยใช้ Java spring-boot

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

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

ที่เก็บ GitHub ของ Tink Java Apps มีคลาสตัวช่วย 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 สตริงข้อมูลที่กำหนดเองตามที่

หากแอปไม่ได้ให้สตริงข้อมูลที่กำหนดเอง ค่าพารามิเตอร์การค้นหานี้จะไม่แสดงในการเรียกกลับของ 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 ตัวระบุที่เข้ารหัสเลขฐาน 16 ที่ไม่ซ้ำสำหรับกิจกรรมการมอบรางวัลแต่ละรายการที่สร้างโดย AdMob 18fa792de1bca816048293fc71035638
user_id ตัวระบุผู้ใช้ตามที่ SetUserId

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

1234567

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

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

ชื่อแหล่งที่มาของโฆษณา รหัสแหล่งที่มาของโฆษณา
อาร์คี (การเสนอราคา)5240798063227064260
การสร้างโฆษณา (การเสนอราคา)1477265452970951479
AdColony15586990674969969776
AdColony (ไม่ใช่ SDK) (การเสนอราคา)4600416542059544716
AdColony (การเสนอราคา)6895345910719072481
AdFalcon3528208921554210682
เครือข่าย AdMob5450213213286189855
ADResult10593873382626181482
AMoAd17253994435944008978
แอปโลวิน1063618907739174004
Applovin (การเสนอราคา)1328079684332308356
ชาร์ตบูสต์2873236629771172317
แพลตฟอร์มช็อกโกแลต (การเสนอราคา)6432849193975106527
CrossChannel (MdotM)9372067028804390441
เหตุการณ์ที่กำหนดเอง18351550913290782395
DT Exchange*
* ก่อนวันที่ 21 กันยายน 2022 เครือข่ายนี้มีชื่อว่า "Fyber Marketplace"
2179455223494392917
EMX (การเสนอราคา)8497809869790333482
แบบไหล (การเสนอราคา)8419777862490735710
Flurry3376427960656545613
Fyber*
* แหล่งที่มาของโฆษณานี้ใช้สำหรับการรายงานข้อมูลย้อนหลัง
4839637394546996422
i-mobile5208827440166355534
ปรับปรุงกลยุทธ์ดิจิทัล (การเสนอราคา)159382223051638006
Index Exchange (การเสนอราคา)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
Nexxen (การเสนอราคา)*

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

2831998725945605450
ONE by AOL (Millennial Media)6101072188699264581
ONE by AOL (Nexage)3224789793037044399
OneTag Exchange (การเสนอราคา)4873891452523427499
OpenX (การเสนอราคา)4918705482605678398
Pangle (การเสนอราคา)3525379893916449117
PubMatic (การเสนอราคา)3841544486172445473
แคมเปญแบบจองล่วงหน้า7068401028668408324
RhythmOne (การเสนอราคา)2831998725945605450
ไอคอนรูบิก (การเสนอราคา)3993193775968767067
ดาวเคราะห์ SK734341340207269415
ส่วนแบ่งผ่าน (การเสนอราคา)5247944089976324188
Smaato (การเสนอราคา)3362360112145450544
สมการ (การเสนอราคา)*

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

5970199210771591442
Sonobi (การเสนอราคา)3270984106996027150
Tapjoy7295217276740746030
TapJo (การเสนอราคา)4692500501762622178
เทนเซนต์ GDT7007906637038700218
TripleLift (การเสนอราคา)8332676245392738510
โฆษณา Unity4970775877303683148
สื่อ Verizon7360851262951344112
Verve Group (การเสนอราคา)5013176581647059185
Vpon1940957084538325905
Liftoff Monetize*

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

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

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

4692500501762622185
Yieldmo (การเสนอราคา)4193081836471107579
YieldOne (การเสนอราคา)3154533971590234104
ซุกส์5506531810221735863

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

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

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

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

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

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

void HandleRewardedAdLoaded(RewardedAd ad, AdFailedToLoadEventArgs error)
{
    // Create and pass the SSV options to the rewarded ad.
    var options = new ServerSideVerificationOptions
                          .Builder()
                          .SetCustomData("SAMPLE_CUSTOM_DATA_STRING")
                          .Build()
    ad.SetServerSideVerificationOptions(options);
}

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

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

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

เรียกคีย์สาธารณะ

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

คุณจะดึงข้อมูลรายการคีย์สาธารณะที่จะใช้ตรวจสอบโค้ดเรียกกลับ 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