بيانات السلامة

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

تنسيق بيان السلامة المعروض

حمولة البيانات بتنسيق 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 رموز مميزة للسلامة أو أقل على هذا الجهاز خلال الساعة الماضية.
LEVEL_2
طلب التطبيق ما بين 11 و25 رمزًا مميزًا من رموز السلامة على هذا الجهاز خلال الساعة الماضية.
LEVEL_3
طلب التطبيق خلال الساعة الماضية ما بين 26 و50 رمزًا مميّزًا للسلامة على هذا الجهاز.
LEVEL_4
أعلى مستوى: طلب التطبيق أكثر من 50 رمزًا مميّزًا للسلامة على هذا الجهاز خلال الساعة الماضية.
UNEVALUATED

لم يتم تقييم مستوى نشاط الجهاز الأخير. وقد يحدث ذلك لعدة أسباب، بما فيها ما يلي:

  • الجهاز غير موثوق بالقدر الكافي
  • لا يعرف Google Play إصدار تطبيقك المثبَّت على الجهاز.
  • حدثت مشاكل فنية في الجهاز.

تكون تعريفات المستوى تقريبية وقابلة للتغيير.

حقل تفاصيل الحساب

يحتوي الحقل 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 للحماية". إذا لم يتمكن المستخدم من تحقيق هذه المتطلبات، يمكنك حظره من إجراء الخادم.