التحقّق من صحة عمليات معاودة الاتصال في ميزة "إثبات الملكية من جهة الخادم"

ردود الاتصال لإثبات الملكية من جهة الخادم هي طلبات عناوين URL تتضمّن مَعلمات طلب بحث توسعها Google، وتُرسِلها Google إلى نظام خارجي لإعلامه بأنّه يجب منح المستخدم مكافأة مقابل التفاعل مع إعلان بيني يضمّ مكافأة أو إعلان بيني يضمّ مكافأة. توفّر طلبات إعادة الاتصال في ميزة "إثبات الملكية من جهة الخادم" (SSV) التي تضمّ مكافأة طبقة حماية إضافية ضد انتحال هوية طلبات إعادة الاتصال من جهة العميل لمكافأة المستخدمين.

يوضّح لك هذا الدليل كيفية التحقّق من عمليات ردّ الاتصال في عملية التحقق من جهة الخادم (SSV) التي تضمّ مكافأة باستخدام مكتبة التشفير التابعة لجهة خارجية Tink Java Apps لضمان أنّ مَعلمات طلب البحث في ردّ الاتصال هي قيم شرعية. على الرغم من أنّه يتم استخدام Tink لأغراض هذا الدليل، يمكنك استخدام أي مكتبة تابعة لجهة خارجية تتيح ECDSA. يمكنك أيضًا اختبار الخادم باستخدام أداة الاختبار في واجهة مستخدم AdMob.

اطّلِع على مثال على نموذج SSV للإعلانات التي تضم مكافآت باستخدام Java spring-boot.

لمكافأة المستخدمين.

المتطلبات الأساسية

استخدام RewardedAdsVerifier من مكتبة تطبيقات Java في Tink

يتضمّن مستودع GitHub الخاص بتطبيقات Tink Java فئة مساعدة RewardedAdsVerifier لتقليل الرمز البرمجي المطلوب للتحقّق من طلب إعادة الاتصال في ميزة "إثبات الهوية من خلال الفيديو" التي تضم مكافأة. يتيح لك استخدام هذه الفئة التحقّق من عنوان 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 سلسلة البيانات المخصّصة كما تقدّمها setCustomData.

إذا لم يقدّم التطبيق سلسلة بيانات مخصّصة، لن تكون قيمة مَعلمة طلب البحث هذه متوفّرة في طلب استدعاء SSV.

SAMPLE_CUSTOM_DATA_STRING
key_id المفتاح الذي سيتم استخدامه للتحقّق من طلب إعادة الاتصال لميزة "التحقّق من المستخدمين عبر رسالة قصيرة" يتم ربط هذه القيمة بمفتاح عام يوفّره خادم مفاتيح AdMob. 1234567890
reward_amount مبلغ المكافأة كما هو محدّد في إعدادات الوحدة الإعلانية. 5
reward_item عنصر المكافأة كما هو محدّد في إعدادات الوحدة الإعلانية عملات معدنية
توقيع توقيع لطلب إعادة الاتصال في SSV تم إنشاؤه بواسطة AdMob MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
timestamp الطابع الزمني الذي حصل فيه المستخدم على المكافأة بتوقيت بداية الحقبة بالمللي ثانية 1507770365237823
transaction_id معرّف فريد بترميز سداسي لكل حدث منح مكافأة يتم إنشاؤه بواسطة AdMob. 18fa792de1bca816048293fc71035638
user_id معرّف المستخدم كما قدّمه setUserId.

إذا لم يقدّم التطبيق معرّف مستخدم، لن تكون مَعلمة طلب البحث هذه متوفّرة في طلب إعادة الاتصال بـ SSV.

1234567

معرّفات مصادر الإعلانات

أسماء مصادر الإعلانات وأرقام تعريفها

اسم مصدر الإعلان رقم تعريف مصدر الإعلان
Aarki (عروض الأسعار)5240798063227064260
إنشاء الإعلانات (عروض الأسعار)1477265452970951479
AdColony15586990674969969776
AdColony (غير حزمة تطوير البرامج (SDK)) (عروض الأسعار)4600416542059544716
AdColony (عروض الأسعار)6895345910719072481
AdFalcon3528208921554210682
شبكة AdMob5450213213286189855
العرض الإعلاني بدون انقطاع في "شبكة AdMob"1215381445328257950
ADResult10593873382626181482
AMoAd17253994435944008978
Applovin1063618907739174004
Applovin (عروض الأسعار)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform (عروض الأسعار)6432849193975106527
CrossChannel (MdotM)9372067028804390441
حدث مخصّص18351550913290782395
DT Exchange*
* قبل 21 أيلول (سبتمبر) 2022، كانت هذه الشبكة تُعرف باسم "Fyber Marketplace".
2179455223494392917
EMX (عروض الأسعار)8497809869790333482
Fluct (عروض الأسعار)8419777862490735710
Flurry3376427960656545613
Fyber*
* يُستخدَم مصدر الإعلان هذا لإعداد التقارير السابقة.
4839637394546996422
i-mobile5208827440166355534
Improve Digital (bidding)159382223051638006
Index Exchange (عروض الأسعار)4100650709078789802
InMobi7681903010231960328
InMobi (عروض الأسعار)6325663098072678541
InMobi Exchange (عروض الأسعار)5264320421916134407
IronSource6925240245545091930
‫ironSource Ads (عروض الأسعار)1643326773739866623
Leadbolt2899150749497968595
LG U+AD18298738678491729107
شبكة إعلانات LINE3025503711505004547
مايو7505118203095108657
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
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
Pangle4069896914521993236
Pangle (عروض الأسعار)3525379893916449117
PubMatic (عروض الأسعار)3841544486172445473
حملة قائمة على الحجز7068401028668408324
RhythmOne (عروض الأسعار)2831998725945605450
Rubicon (عروض الأسعار)3993193775968767067
كوكب SK734341340207269415
Sharethrough (عروض الأسعار)5247944089976324188
Smaato (عروض الأسعار)3362360112145450544
Equativ (عروض الأسعار)*

* قبل 12 كانون الثاني (يناير) 2023، كان اسم هذه الشبكة هو "Smart Adserver".

5970199210771591442
Sonobi (عروض الأسعار)3270984106996027150
Tapjoy7295217276740746030
Tapjoy (عروض الأسعار)4692500501762622178
Tencent GDT7007906637038700218
TripleLift (عروض الأسعار)8332676245392738510
Unity Ads4970775877303683148
Unity Ads (عروض الأسعار)7069338991535737586
Verizon Media7360851262951344112
Verve Group (عروض الأسعار)5013176581647059185
Vpon1940957084538325905
‫Liftoff Monetize*

* قبل 30 كانون الثاني (يناير) 2023، كانت هذه الشبكة تُعرف باسم "Vungle".

1953547073528090325
‫Liftoff Monetize (عروض الأسعار)*

* قبل 30 كانون الثاني (يناير) 2023، كانت هذه الشبكة تُعرف باسم "Vungle (عروض الأسعار)".

4692500501762622185
Yieldmo (عروض الأسعار)4193081836471107579
YieldOne (عروض الأسعار)3154533971590234104
Zucks5506531810221735863

مكافأة المستخدم

من المهمّ موازنة تجربة المستخدم والتحقّق من صحة المكافأة عند تحديد وقت منح المكافأة للمستخدم. قد تحدث تأخيرات في عمليات الاستدعاء من جهة الخادم قبل الوصول إلى الأنظمة الخارجية. لذلك، فإنّ أفضل الممارسات المقترَحة هي استخدام مكالمة الاسترجاع من جهة العميل لمكافأة المستخدم على الفور، مع إجراء عمليات التحقّق من جميع المكافآت عند تلقّي مكالمات الاسترجاع من جهة الخادم. يقدّم هذا الأسلوب تجربة مستخدم جيدة مع ضمان صلاحية المكافآت التي يتم منحها.

ومع ذلك، بالنسبة إلى التطبيقات التي تكون فيها صلاحية المكافأة ضرورية (على سبيل المثال، إذا كانت المكافأة تؤثر في اقتصاد اللعبة داخل تطبيقك) وكان التأخير في منح المكافآت مقبولًا، قد يكون الانتظار إلى أن يتم التحقّق من طلب إعادة الاتصال من جهة الخادم هو أفضل نهج.

البيانات المخصّصة

يجب أن تستخدم التطبيقات التي تتطلّب بيانات إضافية في عمليات ردّ الاتصال لإثبات الملكية من جهة الخادم ميزة البيانات المخصّصة للإعلانات التي تضمّ مكافأة. يتمّ تمرير أيّ قيمة سلسلة تمّ ضبطها على عنصر إعلان مكافأة إلى مَعلمة طلب البحث custom_data في طلب استدعاء SSV. في حال عدم تحديد قيمة data custom، لن تكون قيمة مَعلمة طلب البحث custom_data متوفرة في دالة SSV callback.

يحدِّد المثال التالي خيارات SSV بعد تحميل الإعلان الذي يقدّم مكافأة:

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
  }
})

إذا كنت تريد ضبط سلسلة المكافأة المخصّصة، يجب إجراء ذلك قبل عرض الإعلان.

التحقّق يدويًا من إثبات الملكية من جانب الخادم (SSV) للإعلانات التي تضم مكافآت

في ما يلي الخطوات التي تنفّذها فئة RewardedAdsVerifier للتحقّق من صحة ملف تعريف الارتباط لعرض الإعلانات أثناء التشغيل الذي يتضمّن مكافأة. على الرغم من أنّ مقتطفات الرموز البرمجية المضمّنة مكتوبة بلغة 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;
}

الحصول على المحتوى المطلوب إثبات ملكيته

تكون مَعلمتا طلب البحث الأخيرتان لطلبات إعادة الاتصال في ميزة "الإعلانات المتجاوبة على شبكة البحث" التي تضم مكافآت دائمًا signature وkey_id, بهذا الترتيب. وتحدِّد مَعلمات طلب البحث المتبقية المحتوى الذي يجب التحقّق منه. لنفترض أنّك أعددت AdMob لإرسال عمليات استدعاء المكافآت إلى ‎ https://www.myserver.com/mypath. يعرض المقتطف أدناه مثالاً على طلب تأكيد هوية العميل في عملية شراء مدعومة بالإعلانات مع تمييز المحتوى المطلوب إثبات ملكيته.

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 وفقًا لجدول زمني متبدّل. لضمان استمرار عمل عمليات التحقّق من عمليات تسجيل الإحالات الناجحة باستخدام SSV على النحو المقصود، يجب عدم تخزين المفاتيح العامة في ذاكرة التخزين المؤقت لأكثر من 24 ساعة.
ماذا يحدث إذا تعذّر الوصول إلى خادمنا؟
تتوقع Google تلقّي رمز استجابة حالة النجاح HTTP 200 OK لطلبات SSV المعاد الاتصال بها. إذا تعذّر الوصول إلى خادمك أو لم يقدّم الردّ المتوقّع، ستحاول Google إعادة إرسال طلبات إعادة الاتصال في SSV حتى خمس مرات بفواصل زمنية تبلغ ثانية واحدة.
كيف يمكنني التأكّد من أنّ عمليات تسجيل المكالمات في SSV تأتي من Google؟
استخدِم بحث نظام أسماء النطاقات العكسي للتحقّق من أنّ عمليات استدعاء SSV تأتي من Google.