استدعاءات إثبات الملكية من جانب الخادم هي طلبات عناوين URL، مع توسيع نطاق مَعلمات طلب البحث من خلال Google، والتي ترسلها Google إلى نظام خارجي لإعلامه بأنه يجب مكافأة المستخدم مقابل تفاعله مع إعلان بيني يضم مكافأة أو مكافأة. توفّر استدعاءات SSV (التحقُّق من جهة الخادم) بمكافأة طبقة حماية إضافية ضد انتحال هوية معاودة الاتصال من جهة العميل لمكافأة المستخدمين.
يوضِّح لك هذا الدليل كيفية التحقّق من عمليات استدعاء SSV بمكافأة باستخدام مكتبة التشفير التابعة لجهة خارجية في Tink Java Apps للتأكّد من أن معلَمات طلب البحث في استدعاء الدالة هي قيم سليمة. على الرغم من أنّ تطبيق Tink يُستخدم لأغراض هذا الدليل، يمكنك استخدام أي مكتبة تابعة لجهة خارجية تتيح استخدام ECDSA. ويمكنك أيضًا اختبار خادمك باستخدام أداة الاختبار في واجهة مستخدم AdMob.
راجع هذا المثال الذي يعمل بكامل طاقته باستخدام تمهيد Java النابض.
المتطلبات الأساسية
يمكنك دمج الإعلانات بمكافأة في تطبيقك المتوافق مع الأجهزة الجوّالة باستخدام الإصدار 7.28.0 أو إصدار أحدث من "SDK لإعلانات Google على الأجهزة الجوّالة"
فعِّل التحقق من جهة الخادم بمكافأة على وحدتك الإعلانية.
استخدام 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 | سلسلة بيانات مخصّصة على النحو المقدَّم من
customRewardString
.
إذا لم يوفّر التطبيق سلسلة بيانات مخصّصة، لن تتوفّر قيمة مَعلمة طلب البحث هذه في استدعاء 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 | معرّف المستخدم على النحو المقدَّم من
userIdentifier :
إذا لم يوفّر التطبيق أي معرّف مستخدم، لن تكون مَعلمة طلب البحث هذه متوفّرة في استدعاء SSV. |
1234567 |
معرّفات مصادر الإعلانات
أسماء مصادر الإعلانات وأرقام تعريفها
اسم مصدر الإعلان | رقم تعريف مصدر الإعلان |
---|---|
Aarki (عروض الأسعار) | 5240798063227064260 |
إنشاء الإعلانات (عروض الأسعار) | 1477265452970951479 |
AdColony | 15586990674969969776 |
AdColony (غير مستندة إلى حزمة تطوير البرامج (SDK)) (عروض الأسعار) | 4600416542059544716 |
AdColony (عروض الأسعار) | 6895345910719072481 |
AdFalcon | 3528208921554210682 |
شبكة AdMob | 5450213213286189855 |
ADResult | 10593873382626181482 |
AMoAd | 17253994435944008978 |
أبلوفين | 1063618907739174004 |
Apploin (عروض الأسعار) | 1328079684332308356 |
كشافة الرسم البياني | 2873236629771172317 |
منصّة الشوكولاتة (عروض الأسعار) | 6432849193975106527 |
قناة CrossChannel (MdotM) | 9372067028804390441 |
حدث مخصّص | 18351550913290782395 |
DT Exchange* * قبل 21 أيلول (سبتمبر) 2022، كان يُطلق على هذه الشبكة اسم "سوق Fyber". | 2179455223494392917 |
EMX (عروض الأسعار) | 8497809869790333482 |
متقلّب (عروض الأسعار) | 8419777862490735710 |
Flurry | 3376427960656545613 |
Fyber* * يُستخدَم مصدر الإعلان هذا في إعداد التقارير السابقة. | 4839637394546996422 |
i-mobile | 5208827440166355534 |
التحسين الرقمي (عروض الأسعار) | 159382223051638006 |
بورصة الفهرس (عروض الأسعار) | 4100650709078789802 |
InMobi | 7681903010231960328 |
InMobi (عروض الأسعار) | 6325663098072678541 |
IronSource | 6925240245545091930 |
إعلانات ironSource (عروض الأسعار) | 1643326773739866623 |
Leadbolt | 2899150749497968595 |
LG U+AD | 18298738678491729107 |
شبكة LINE Ads | 3025503711505004547 |
مايو | 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 |
MobFox | 8079529624516381459 |
MobFox (عروض الأسعار) | 3086513548163922365 |
MoPub (متوقّف نهائيًا) | 10872986198578383917 |
myTarget | 8450873672465271579 |
Nend | 9383070032774777750 |
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 |
Rubicon (عروض الأسعار) | 3993193775968767067 |
كوكب ساسكاتشوان | 734341340207269415 |
نسبة مشاركة الإعلان بالكامل (عروض الأسعار) | 5247944089976324188 |
Smaato (عروض الأسعار) | 3362360112145450544 |
Equativ (عروض الأسعار)* * قبل 12 كانون الثاني (يناير) 2023، كان يُطلق على هذه الشبكة اسم "خادم الإعلانات الذكي". | 5970199210771591442 |
Sonobi (عروض الأسعار) | 3270984106996027150 |
Tapjoy | 7295217276740746030 |
Tapjoy (عروض الأسعار) | 4692500501762622178 |
تينسنت GDT | 7007906637038700218 |
TripleLift (عروض الأسعار) | 8332676245392738510 |
إعلانات Unity | 4970775877303683148 |
Unity Ads (عروض الأسعار) | 7069338991535737586 |
شركة Verizon Media | 7360851262951344112 |
مجموعة Verve (عروض الأسعار) | 5013176581647059185 |
فون | 1940957084538325905 |
Liftoff Monetize* * قبل 30 كانون الثاني (يناير) 2023، كان يُطلق على هذه الشبكة اسم "Vungle". | 1953547073528090325 |
Liftoff Monetize (عروض الأسعار)* * قبل 30 كانون الثاني (يناير) 2023، كان يُطلق على هذه الشبكة اسم "التفاعل (عروض الأسعار)". | 4692500501762622185 |
الأرباح (عروض الأسعار) | 4193081836471107579 |
YieldOne (عروض الأسعار) | 3154533971590234104 |
Zucks | 5506531810221735863 |
مكافأة المستخدم
من المهم تحقيق التوازن بين تجربة المستخدم ومكافأة التحقق من الصحة عند تحديد وقت مكافأة المستخدم. قد تواجه عمليات معاودة الاتصال من جانب الخادم تأخيرات قبل الوصول إلى الأنظمة الخارجية. وبالتالي، فإنّ أفضل الممارسات الموصى بها هي استخدام معاودة الاتصال من جهة العميل لمكافأة المستخدم على الفور مع إجراء التحقُّق من جميع المكافآت عند استلام استدعاءات من جهة الخادم. يوفر هذا النهج تجربة مستخدم جيدة مع ضمان صحة المكافآت الممنوحة.
ومع ذلك، بالنسبة إلى الطلبات التي تكون فيها صلاحية المكافآت أمرًا بالغ الأهمية (على سبيل المثال، تؤثر المكافأة على اقتصاد تطبيقك داخل اللعبة) وحالات التأخير في منح المكافآت مقبولة، فقد يكون انتظار معاودة الاتصال التي تم التحقق منها من جهة الخادم هو النهج الأفضل.
البيانات المخصّصة
إنّ التطبيقات التي تتطلّب بيانات إضافية في عمليات معاودة الاتصال لإثبات الملكية من جهة الخادم يجب أن تستخدم ميزة البيانات المخصّصة للإعلانات التي تضم مكافأة. يتمّ تمرير أيّ قيمة سلسلة يتمّ ضبطها على عنصر إعلان
بمكافأة إلى معلَمة طلب البحث custom_data
الخاصة باستدعاء SSV. إذا لم يتم ضبط أي قيمة لبيانات مخصّصة، لن تكون قيمة مَعلمة طلب البحث custom_data
متوفّرة في استدعاء SSV.
يوضح نموذج الرمز البرمجي التالي كيفية ضبط خيارات SSV بعد تحميل الإعلان بمكافأة.
Swift
GADRewardedAd.load(withAdUnitID:"ca-app-pub-3940256099942544/1712485313", request: request, completionHandler: { [self] ad, error in if let error != error { rewardedAd = ad let options = GADServerSideVerificationOptions() options.customRewardString = "SAMPLE_CUSTOM_DATA_STRING" rewardedAd.serverSideVerificationOptions = options }
Objective-C
GADRequest *request = [GADRequest request]; [GADRewardedAd loadWithAdUnitID:@"ca-app-pub-3940256099942544/1712485313" request:request completionHandler:^(GADRewardedAd *ad, NSError *error) { if (error) { // Handle Error return; } self.rewardedAd = ad; GADServerSideVerificationOptions *options = [[GADServerSideVerificationOptions alloc] init]; options.customRewardString = @"SAMPLE_CUSTOM_DATA_STRING"; ad.serverSideVerificationOptions = options; }];
إثبات الملكية اليدوي لميزة 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 ×tamp=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.