تصف هذه الصفحة كيفية تفسير بيان السلامة الذي يتم إرجاعه والعمل عليه. وسواء أرسلت طلبًا عاديًا أو كلاسيكيًا من واجهة برمجة التطبيقات، يتم عرض بيان السلامة بالتنسيق نفسه مع محتوى مشابه. ينقل بيان السلامة معلومات حول صلاحية الأجهزة والتطبيقات والحسابات. يمكن لخادم تطبيقك استخدام الحمولة الناتجة في بيان تم فك تشفيره وتم التحقق منه لتحديد أفضل السبل لمتابعة إجراء أو طلب معين في تطبيقك.
تنسيق بيان السلامة المعروض
حمولة البيانات بتنسيق JSON بتنسيق نص عادي وتحتوي على إشارات سلامة بالإضافة إلى المعلومات التي يقدّمها المطوّر.
في ما يلي البنية العامة للحمولة:
{ requestDetails: { ... } appIntegrity: { ... } deviceIntegrity: { ... } accountDetails: { ... } environmentDetails: { ... } }
يجب أولاً التحقّق من أنّ القيم في الحقل requestDetails
تتطابق مع قيم الطلب الأصلي قبل التحقّق من كل بيان سلامة. تصف الأقسام التالية كل حقل
بمزيد من التفصيل.
حقل تفاصيل الطلب
ويحتوي الحقل requestDetails
على معلومات حول الطلب، بما في ذلك المعلومات التي يقدّمها المطوّر في requestHash
للطلبات العادية والحقل nonce
للطلبات الكلاسيكية.
بالنسبة إلى طلبات البيانات من واجهة برمجة التطبيقات العادية:
requestDetails: { // Application package name this attestation was requested for. // Note that this field might be spoofed in the middle of the request. requestPackageName: "com.package.name" // Request hash provided by the developer. requestHash: "aGVsbG8gd29scmQgdGhlcmU" // The timestamp in milliseconds when the integrity token // was prepared (computed on the server). timestampMillis: "1675655009345" }
يجب أن تتطابق هذه القيم مع قيم الطلب الأصلي. لذلك، تحقق من جزء
requestDetails
من حمولة JSON عن طريق التأكد من تطابق
الrequestPackageName
وrequestHash
مع ما تم إرساله في الطلب الأصلي، كما هو موضّح في مقتطف الرمز التالي:
Kotlin
val requestDetails = JSONObject(payload).getJSONObject("requestDetails") val requestPackageName = requestDetails.getString("requestPackageName") val requestHash = requestDetails.getString("requestHash") val timestampMillis = requestDetails.getLong("timestampMillis") val currentTimestampMillis = ... // Ensure the token is from your app. if (!requestPackageName.equals(expectedPackageName) // Ensure the token is for this specific request || !requestHash.equals(expectedRequestHash) // Ensure the freshness of the token. || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) { // The token is invalid! See below for further checks. ... }
Java
RequestDetails requestDetails = decodeIntegrityTokenResponse .getTokenPayloadExternal() .getRequestDetails(); String requestPackageName = requestDetails.getRequestPackageName(); String requestHash = requestDetails.getRequestHash(); long timestampMillis = requestDetails.getTimestampMillis(); long currentTimestampMillis = ...; // Ensure the token is from your app. if (!requestPackageName.equals(expectedPackageName) // Ensure the token is for this specific request. || !requestHash.equals(expectedRequestHash) // Ensure the freshness of the token. || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) { // The token is invalid! See below for further checks. ... }
لطلبات البيانات من واجهة برمجة التطبيقات الكلاسيكية:
requestDetails: { // Application package name this attestation was requested for. // Note that this field might be spoofed in the middle of the // request. requestPackageName: "com.package.name" // base64-encoded URL-safe no-wrap nonce provided by the developer. nonce: "aGVsbG8gd29scmQgdGhlcmU" // The timestamp in milliseconds when the request was made // (computed on the server). timestampMillis: "1617893780" }
يجب أن تتطابق هذه القيم مع قيم الطلب الأصلي. لذلك، تحقق من جزء requestDetails
من حمولة JSON عن طريق التأكد من تطابق requestPackageName
وnonce
مع ما تم إرساله في الطلب الأصلي، كما هو موضح في مقتطف الرمز التالي:
Kotlin
val requestDetails = JSONObject(payload).getJSONObject("requestDetails") val requestPackageName = requestDetails.getString("requestPackageName") val nonce = requestDetails.getString("nonce") val timestampMillis = requestDetails.getLong("timestampMillis") val currentTimestampMillis = ... // Ensure the token is from your app. if (!requestPackageName.equals(expectedPackageName) // Ensure the token is for this specific request. See 'Generate a nonce' // section of the doc on how to store/compute the expected nonce. || !nonce.equals(expectedNonce) // Ensure the freshness of the token. || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) { // The token is invalid! See below for further checks. ... }
Java
JSONObject requestDetails = new JSONObject(payload).getJSONObject("requestDetails"); String requestPackageName = requestDetails.getString("requestPackageName"); String nonce = requestDetails.getString("nonce"); long timestampMillis = requestDetails.getLong("timestampMillis"); long currentTimestampMillis = ...; // Ensure the token is from your app. if (!requestPackageName.equals(expectedPackageName) // Ensure the token is for this specific request. See 'Generate a nonce' // section of the doc on how to store/compute the expected nonce. || !nonce.equals(expectedNonce) // Ensure the freshness of the token. || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) { // The token is invalid! See below for further checks. ... }
حقل سلامة التطبيق
يحتوي الحقل appIntegrity
على معلومات متعلقة بالحزمة.
appIntegrity: { // PLAY_RECOGNIZED, UNRECOGNIZED_VERSION, or UNEVALUATED. appRecognitionVerdict: "PLAY_RECOGNIZED" // The package name of the app. // This field is populated iff appRecognitionVerdict != UNEVALUATED. packageName: "com.package.name" // The sha256 digest of app certificates (base64-encoded URL-safe). // This field is populated iff appRecognitionVerdict != UNEVALUATED. certificateSha256Digest: ["6a6a1474b5cbbb2b1aa57e0bc3"] // The version of the app. // This field is populated iff appRecognitionVerdict != UNEVALUATED. versionCode: "42" }
يمكن أن يحتوي appRecognitionVerdict
على القيم التالية:
PLAY_RECOGNIZED
- يتطابق التطبيق والشهادة مع الإصدارات التي تم توزيعها من خلال Google Play.
UNRECOGNIZED_VERSION
- لا يتطابق اسم الشهادة أو اسم الحزمة مع سجلّات Google Play.
UNEVALUATED
- لم يتم تقييم سلامة التطبيق. تم إغفال أحد المتطلبات الضرورية، مثل أن الجهاز لم يكن جديرًا بالثقة بما يكفي.
للتأكُّد من إنشاء الرمز المميّز بواسطة تطبيق أنشأته بنفسك، تأكَّد من أنّ سلامة التطبيق كما هو متوقَّع، كما هو موضَّح في مقتطف الرمز التالي:
Kotlin
val appIntegrity = JSONObject(payload).getJSONObject("appIntegrity") val appRecognitionVerdict = appIntegrity.getString("appRecognitionVerdict") if (appRecognitionVerdict == "PLAY_RECOGNIZED") { // Looks good! }
Java
JSONObject appIntegrity = new JSONObject(payload).getJSONObject("appIntegrity"); String appRecognitionVerdict = appIntegrity.getString("appRecognitionVerdict"); if (appRecognitionVerdict.equals("PLAY_RECOGNIZED")) { // Looks good! }
يمكنك أيضًا التحقّق من اسم حزمة التطبيقات وإصدار التطبيق وشهادات التطبيق يدويًا.
حقل سلامة الجهاز
ويمكن أن يحتوي الحقل deviceIntegrity
على قيمة واحدة، وهي deviceRecognitionVerdict
، تشمل تصنيفًا واحدًا أو أكثر يمثّل مدى فرض جهاز للحفاظ على سلامة التطبيق. إذا لم يستوفِ الجهاز معايير أي تصنيف، سيكون الحقل deviceIntegrity
فارغًا.
deviceIntegrity: { // "MEETS_DEVICE_INTEGRITY" is one of several possible values. deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"] }
تلقائيًا، يمكن أن يحتوي deviceRecognitionVerdict
على ما يلي:
MEETS_DEVICE_INTEGRITY
- يعمل التطبيق على جهاز يعمل بنظام التشغيل Android ويتضمّن "خدمات Google Play". يجتاز الجهاز عمليات فحص سلامة النظام ويلبّي متطلبات التوافق مع Android.
- فارغة (قيمة فارغة)
- التطبيق يعمل على جهاز به علامات تشير إلى هجمات (مثل الاتصال بواجهة برمجة التطبيقات) أو اختراق النظام (مثل الوصول إلى الجذر)، أو أنّ التطبيق لا يعمل على جهاز مادي (مثل محاكي لا يجتاز عمليات التحقق من سلامة Google Play)
للتأكُّد من أنّ الرمز المميّز تم الحصول عليه من جهاز موثوق، تأكَّد من أنّ الرمز deviceRecognitionVerdict
هو كما هو متوقع، كما هو موضّح في مقتطف الرمز التالي:
Kotlin
val deviceIntegrity = JSONObject(payload).getJSONObject("deviceIntegrity") val deviceRecognitionVerdict = if (deviceIntegrity.has("deviceRecognitionVerdict")) { deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString() } else { "" } if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) { // Looks good! }
Java
JSONObject deviceIntegrity = new JSONObject(payload).getJSONObject("deviceIntegrity"); String deviceRecognitionVerdict = deviceIntegrity.has("deviceRecognitionVerdict") ? deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString() : ""; if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) { // Looks good! }
إذا كنت تواجه مشاكل في اختبار سلامة جهاز الاجتماع، يُرجى التأكّد من تثبيت برنامج القراءة فقط على الإعدادات الأصلية (على سبيل المثال، من خلال إعادة ضبط الجهاز) وأن يكون برنامج الإقلاع مقفل. يمكنك أيضًا إنشاء اختبارات واجهة برمجة التطبيقات Play Integrity API في Play Console.
التصنيفات الشرطية للأجهزة
إذا تم طرح تطبيقك في برنامج "ألعاب Google Play على الكمبيوتر"، يمكن أن يتضمّن
deviceRecognitionVerdict
أيضًا التصنيف التالي:
MEETS_VIRTUAL_INTEGRITY
- يعمل التطبيق على محاكي يعمل بنظام التشغيل Android ويتضمّن "خدمات Google Play". يجتاز المحاكي عمليات فحص سلامة النظام ويلبي متطلبات التوافق الأساسية مع Android.
معلومات الجهاز الاختيارية
في حال الموافقة على تلقّي تصنيفات إضافية في بيان السلامة، يمكن أن يتضمّن تصنيف deviceRecognitionVerdict
التصنيفات الإضافية التالية:
MEETS_BASIC_INTEGRITY
- التطبيق يعمل على جهاز يجتاز عمليات التحقق الأساسية من سلامة النظام. قد لا يستوفي الجهاز متطلبات التوافق مع Android وقد لا تتم الموافقة عليه لتشغيل "خدمات Google Play". على سبيل المثال، قد يكون الجهاز يعمل بإصدار غير معروف من نظام التشغيل Android، أو قد يحتوي على برنامج إقلاع تم فتح قفله، أو ربما لم يتم اعتماده من قِبل الشركة المصنّعة.
MEETS_STRONG_INTEGRITY
- يعمل التطبيق على جهاز يعمل بنظام التشغيل Android ويتضمّن خدمات Google Play، وله ضمان قوي لسلامة النظام، مثل دليل مستند إلى الأجهزة يثبت سلامة التشغيل. يجتاز الجهاز عمليات فحص سلامة النظام ويلبي متطلبات التوافق مع Android.
سيعرض جهاز واحد تصنيفات متعددة للأجهزة في بيان سلامة الجهاز في حال استيفاء كل معيار من معايير التصنيف.
أحدث أنشطة الجهاز (ميزة تجريبية)
يمكنك أيضًا الموافقة على أحدث أنشطة الجهاز، ما يوضّح عدد المرات التي طلب فيها تطبيقك رمزًا مميّزًا للسلامة على جهاز معيّن خلال الساعة الماضية. يمكنك استخدام نشاط الجهاز الأخير لحماية تطبيقك من الأجهزة غير المتوقعة شديدة النشاط التي قد تشير إلى هجوم نشط. يمكنك تحديد مدى ثقة المستخدم في كل مستوى من مستويات النشاط الأخير للجهاز بناءً على عدد المرات التي تتوقّع أن يتم فيها تثبيت تطبيقك على جهاز عادي عند طلب رمز مميّز للأمان كل ساعة.
إذا فعّلت خيار تلقّي recentDeviceActivity
، سيحتوي الحقل deviceIntegrity
على قيمتَين:
deviceIntegrity: { deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"] recentDeviceActivity: { // "LEVEL_2" is one of several possible values. deviceActivityLevel: "LEVEL_2" } }
تختلف تعريفات deviceActivityLevel
بين الأوضاع ويمكن أن تحتوي على إحدى القيم التالية:
مستوى النشاط الأخير للجهاز | طلب بيانات عادي من واجهة برمجة التطبيقات | طلب واجهة برمجة التطبيقات الكلاسيكية |
---|---|---|
LEVEL_1 (الأدنى) |
طلب التطبيق 10 رموز مميزة للسلامة أو أقل على هذا الجهاز خلال الساعة الأخيرة. | طلب التطبيق الحصول على 5 رموز مميزة أو أقل للحفاظ على السلامة على هذا الجهاز خلال الساعة الأخيرة. |
LEVEL_2 |
طلب التطبيق ما بين 11 و25 رمزًا مميّزًا للسلامة على هذا الجهاز خلال الساعة الماضية. | طلب التطبيق ما بين 6 و15 رمزًا مميّزًا للسلامة على هذا الجهاز خلال الساعة الماضية. |
LEVEL_3 |
طلب التطبيق خلال الساعة الماضية ما بين 26 و50 رمزًا مميّزًا للسلامة على هذا الجهاز. | طلب التطبيق خلال الساعة الماضية ما بين 16 و30 رمزًا مميّزًا للسلامة على هذا الجهاز. |
LEVEL_4 (الأعلى) |
طلب التطبيق أكثر من 50 رمزًا مميّزًا للسلامة على هذا الجهاز خلال الساعة الماضية. | طلب التطبيق أكثر من 30 رمزًا مميّزًا للسلامة على هذا الجهاز خلال الساعة الماضية. |
UNEVALUATED |
لم يتم تقييم نشاط الجهاز الأخير.
وقد يحدث ذلك لعدة أسباب، بما فيها ما يلي:
|
حقل تفاصيل الحساب
يحتوي الحقل accountDetails
على قيمة واحدة، وهي appLicensingVerdict
، تمثّل حالة ترخيص التطبيق.
accountDetails: { // This field can be LICENSED, UNLICENSED, or UNEVALUATED. appLicensingVerdict: "LICENSED" }
يمكن أن تحتوي السمة appLicensingVerdict
على إحدى القيم التالية:
LICENSED
- لدى المستخدم استحقاق تطبيق. بعبارة أخرى، ثبّت المستخدم تطبيقك أو اشتراه من Google Play.
UNLICENSED
- لا يملك المستخدم إذنًا بالوصول إلى التطبيقات. ويحدث ذلك مثلاً عندما يثبّت المستخدِم تطبيقك من مصدر غير معروف أو لا يكتسبه من Google Play. يمكنك عرض مربّع الحوار GET_LICENSED للمستخدمين لحلّ هذه المشكلة.
UNEVALUATED
لم يتم تقييم تفاصيل الترخيص بسبب عدم استيفاء أحد المتطلبات الضرورية.
وقد يحدث ذلك لعدة أسباب، بما فيها ما يلي:
- الجهاز غير موثوق بالقدر الكافي
- لا يعرف Google Play إصدار تطبيقك المثبَّت على الجهاز.
- لم يسجّل المستخدم الدخول إلى Google Play.
للتأكُّد من أنّ المستخدم لديه أذونات الوصول إلى تطبيقك، تأكَّد من أنّ
appLicensingVerdict
على النحو المتوقّع، كما هو موضَّح في مقتطف الرمز التالي:
Kotlin
val accountDetails = JSONObject(payload).getJSONObject("accountDetails") val appLicensingVerdict = accountDetails.getString("appLicensingVerdict") if (appLicensingVerdict == "LICENSED") { // Looks good! }
Java
JSONObject accountDetails = new JSONObject(payload).getJSONObject("accountDetails"); String appLicensingVerdict = accountDetails.getString("appLicensingVerdict"); if (appLicensingVerdict.equals("LICENSED")) { // Looks good! }
حقل تفاصيل البيئة
إذا فعّلت بيان "Play للحماية" في Google Play Console،
ستتضمّن الاستجابة من واجهة برمجة التطبيقات الحقل environmentDetails
. يحتوي الحقل
environmentDetails
على قيمة واحدة، وهي playProtectVerdict
،
توفّر معلومات حول "Google Play للحماية" على الجهاز.
environmentDetails: { playProtectVerdict: "NO_ISSUES" }
يمكن أن تحتوي السمة playProtectVerdict
على إحدى القيم التالية:
NO_ISSUES
- تم تفعيل "Play للحماية" ولم ترصد أي مشاكل في التطبيق على الجهاز.
NO_DATA
- تم تفعيل "Play للحماية" ولكن لم يتم إجراء أي فحص حتى الآن. من المحتمل أنّه تمّت إعادة ضبط الجهاز أو تطبيق "متجر Play" مؤخرًا.
POSSIBLE_RISK
- تم إيقاف "Play للحماية".
MEDIUM_RISK
- تم تفعيل خدمة "Play للحماية" ورصدت التطبيقات التي تم تثبيتها على الجهاز والتي قد تتسبّب بضرر.
HIGH_RISK
- تم تفعيل خدمة "Play للحماية" ورصدت تطبيقات خطيرة تم تثبيتها على الجهاز.
UNEVALUATED
لم يتم تقييم بيان "Play للحماية".
وقد يحدث ذلك لعدة أسباب، بما فيها ما يلي:
- الجهاز غير موثوق بالقدر الكافي
- الألعاب فقط: حساب المستخدم ليس
LICENSED
.
إرشادات حول كيفية استخدام بيان "Play للحماية"
يمكن لخادم الخلفية لتطبيقك أن يقرّر ما إذا كان يجب التصرّف بناءً على بيان السلامة بناءً على مدى تحملك للمخاطر. في ما يلي بعض الاقتراحات والإجراءات المحتملة للمستخدم:
NO_ISSUES
- تم تفعيل "Play للحماية" ولم ترصد أي مشاكل، لذلك ليس مطلوبًا من المستخدم اتّخاذ أي إجراء.
POSSIBLE_RISK
وNO_DATA
- عند تلقّي هذه الشهادات، اطلب من المستخدم التأكّد من أنّ خدمة "Play للحماية" مفعّلة
وأنها قد أجرت فحصًا. يجب أن يظهر
NO_DATA
في حالات نادرة فقط. MEDIUM_RISK
وHIGH_RISK
- بناءً على مدى تحملك للمخاطر، يمكنك أن تطلب من المستخدم تشغيل "Play للحماية" واتخاذ إجراء بشأن تحذيرات "Play للحماية". إذا لم يتمكن المستخدم من تحقيق هذه المتطلبات، يمكنك حظره من إجراء الخادم.