التحقق من استدعاءات التحقق من جانب الخادم (SSV)

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

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

راجع هذا المثال الذي يعمل بكامل طاقته باستخدام تمهيد Java النابض.

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

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

يتضمّن مستودع 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 سلسلة بيانات مخصّصة على النحو المقدَّم من setCustomData .

إذا لم يوفّر التطبيق سلسلة بيانات مخصّصة، لن تتوفّر قيمة مَعلمة طلب البحث هذه في استدعاء 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
timestamp طابع زمني لوقت منح المستخدم كوقت Epoch بالمللي ثانية. 1507770365237823
transaction_id معرّف فريد بترميز سداسي لكل حدث منحة مكافأة ينشئه AdMob. 18fa792de1bca816048293fc71035638
user_id معرّف المستخدم على النحو المقدَّم من setUserId.

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

1234567

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

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

اسم مصدر الإعلان رقم تعريف مصدر الإعلان
أركي (عروض الأسعار)5240798063227064260
إنشاء الإعلانات (عروض الأسعار)1477265452970951479
AdColony15586990674969969776
AdColony (غير إصدار حزمة تطوير البرامج (SDK)) (عروض الأسعار)4600416542059544716
AdColony (عروض الأسعار)6895345910719072481
AdFalcon3528208921554210682
شبكة AdMob5450213213286189855
ADResult10593873382626181482
AMoAd17253994435944008978
أبلوفين1063618907739174004
أبلوفين (عروض أسعار)1328079684332308356
مجموعة Chartboost2873236629771172317
منصّة الشوكولاتة (عروض الأسعار)6432849193975106527
CrossChannel (MdotM)9372067028804390441
حدث مخصّص18351550913290782395
DT Exchange*
* قبل 21 أيلول (سبتمبر) 2022، كانت هذه الشبكة تُسمّى "Fyber Marketplace".
2179455223494392917
EMX (عرض الأسعار)8497809869790333482
تقلبات (عرض الأسعار)8419777862490735710
Flurry3376427960656545613
Fyber*
* يتم استخدام مصدر الإعلان هذا لإعداد التقارير السابقة.
4839637394546996422
i-mobile5208827440166355534
تحسين الحملات الرقمية (عروض الأسعار)159382223051638006
تبادل الفهرس (عروض الأسعار)4100650709078789802
InMobi7681903010231960328
InMobi (عروض الأسعار)6325663098072678541
IronSource6925240245545091930
Leadbolt2899150749497968595
إعلان LG U+18298738678491729107
شبكة إعلانات LINE3025503711505004547
مايو7505118203095108657
مايو (عروض الأسعار)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
كوكب سلوفاكيا734341340207269415
المشاركة (عروض الأسعار)5247944089976324188
Smaato (عروض الأسعار)3362360112145450544
Equativ (عروض الأسعار)*

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

5970199210771591442
سونوبي (عروض الأسعار)3270984106996027150
Tapjoy7295217276740746030
Tapjoy (عروض الأسعار)4692500501762622178
شهادة Tencent GDT7007906637038700218
TripleLift (عرض أسعار)8332676245392738510
إعلانات الوحدة4970775877303683148
UnrulyX (عرض الأسعار)2831998725945605450
شركة Verizon Media7360851262951344112
مجموعة Verve (عروض الأسعار)5013176581647059185
فبون1940957084538325905
Liftoff Monetize*

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

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

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

4692500501762622185
تحقيق الأرباح (عروض الأسعار)4193081836471107579
YieldOne (عرض أسعار)3154533971590234104
Zucks5506531810221735863

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

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

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

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

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

يوضح نموذج الرمز البرمجي التالي كيفية ضبط خيارات SSV بعد تحميل الإعلان بمكافأة.

Java

RewardedAd.load(MainActivity.this, "ca-app-pub-3940256099942544/5354046379",
    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, "ca-app-pub-3940256099942544/5354046379",
    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 لإثبات ملكية الحساب الذي يتضمّن مكافأة SSV. على الرغم من أنّ مقتطفات الرمز المضمّنة متوفّرة بلغة Java وتستفيد من مكتبة Tink التابعة لجهة خارجية في Tink، يمكنك تنفيذ هذه الخطوات باللغة التي تختارها، وذلك باستخدام أي مكتبة تابعة لجهة خارجية توفّر ECDSA.

استرجاع المفاتيح العامة

للتحقّق من معاودة الاتصال بمكافأة من خلال ميزة "التحقُّق بخطوتين"، تحتاج إلى مفتاح عام توفّره 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;
}

طلب إثبات ملكية المحتوى

آخر مَعلمتَي طلب بحث لاستدعاءات SSV بمكافأة هما دائمًا 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.