На этой странице описывается, как интерпретировать полученный вердикт целостности и работать с ним. Независимо от того, делаете ли вы стандартный или классический запрос API, вердикт целостности возвращается в том же формате с похожим содержимым. Вердикт целостности передает информацию о действительности устройств, приложений и учетных записей. Сервер вашего приложения может использовать полученные полезные данные в виде расшифрованного и проверенного вердикта, чтобы определить, как лучше всего выполнить определенное действие или запрос в вашем приложении.
Возвращаемый формат вердикта целостности
Полезная нагрузка представляет собой обычный текст JSON и содержит сигналы целостности наряду с информацией, предоставленной разработчиком.
Общая структура полезной нагрузки выглядит следующим образом:
{ requestDetails: { ... } appIntegrity: { ... } deviceIntegrity: { ... } accountDetails: { ... } environmentDetails: { ... } }
Прежде чем проверять каждый вердикт целостности, необходимо сначала убедиться, что значения в поле requestDetails
совпадают со значениями исходного запроса. В следующих разделах каждое поле описывается более подробно.
Поле сведений о запросе
Поле requestDetails
содержит информацию о запросе, включая предоставленную разработчиком информацию в requestHash
для стандартных запросов и nonce
для классических запросов.
Для стандартных запросов API:
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 requested. timestampMillis: "1675655009345" }
Эти значения должны соответствовать значениям исходного запроса. Поэтому проверьте часть requestDetails
полезных данных JSON, убедившись, что requestPackageName
и requestHash
соответствуют тому, что было отправлено в исходном запросе, как показано в следующем фрагменте кода:
Котлин
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. ... }
Ява
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. ... }
Для классических запросов API:
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
соответствуют тому, что было отправлено в исходном запросе, как показано в следующем фрагменте кода:
Котлин
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. ... }
Ява
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
- Целостность приложения не оценивалась. Было пропущено необходимое требование, например, устройство не заслуживало достаточного доверия.
Чтобы убедиться, что токен был создан созданным вами приложением, убедитесь, что целостность приложения соответствует ожиданиям, как показано в следующем фрагменте кода:
Котлин
val appIntegrity = JSONObject(payload).getJSONObject("appIntegrity") val appRecognitionVerdict = appIntegrity.getString("appRecognitionVerdict") if (appRecognitionVerdict == "PLAY_RECOGNIZED") { // Looks good! }
Ява
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.
- Пусто (пустое значение)
- Приложение работает на устройстве, имеющем признаки атаки (например, перехват API) или взлома системы (например, рутирование), или приложение не работает на физическом устройстве (например, эмулятор, который не проходит проверку целостности Google Play). чеки).
Чтобы убедиться, что токен получен от надежного устройства, убедитесь, что значение deviceRecognitionVerdict
соответствует ожидаемому, как показано в следующем фрагменте кода:
Котлин
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! }
Ява
JSONObject deviceIntegrity = new JSONObject(payload).getJSONObject("deviceIntegrity"); String deviceRecognitionVerdict = deviceIntegrity.has("deviceRecognitionVerdict") ? deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString() : ""; if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) { // Looks good! }
Если у вас возникли проблемы с целостностью устройства для тестирования, убедитесь, что установлено заводское ПЗУ (например, путем перезагрузки устройства) и что загрузчик заблокирован. Вы также можете создавать тесты API целостности Play в своей консоли Play .
Условные метки устройств
Если ваше приложение выпускается в Google Play Games для ПК , 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
различаются в зависимости от режима и могут иметь одно из следующих значений:
Недавний уровень активности устройства | Стандартные запросы токена целостности API на этом устройстве за последний час для каждого приложения | Запросы токена целостности классического API на этом устройстве за последний час для каждого приложения |
---|---|---|
LEVEL_1 (самый низкий) | 10 или меньше | 5 или меньше |
LEVEL_2 | Между 11 и 25 | Между 6 и 10 |
LEVEL_3 | От 26 до 50 | Между 11 и 15 |
LEVEL_4 (самый высокий) | Более 50 | Более 15 |
UNEVALUATED | Недавняя активность устройства не оценивалась. Это может произойти потому, что:
|
Атрибуты устройства
Вы также можете выбрать атрибуты устройства, в которых указывается версия Android SDK операционной системы Android, работающей на устройстве. В будущем он может быть расширен другими атрибутами устройства.
Значение версии SDK — это номер версии Android SDK, определенный в Build.VERSION_CODES
. Версия SDK не оценивается, если необходимое требование было пропущено. В этом случае поле sdkVersion
не установлено; таким образом, поле deviceAttributes
пусто. Это может произойти потому, что:
- Устройство не заслуживает доверия.
- Версия вашего приложения, установленного на устройстве, неизвестна Google Play.
- На устройстве возникли технические проблемы.
Если вы согласитесь получать deviceAttributes
, поле deviceIntegrity
будет иметь следующее дополнительное поле:
deviceIntegrity: { deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"] deviceAttributes: { // 33 is one possible value, which represents Android 13 (Tiramisu). sdkVersion: 33 } }
Если версия SDK не оценивается, поле deviceAttributes
будет установлено следующим образом:
deviceIntegrity: { deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"] deviceAttributes: {} // sdkVersion field is not set. }
Поле сведений об учетной записи
Поле accountDetails
содержит одно значение appLicensingVerdict
, которое представляет статус лицензирования приложения Google Play для учетной записи пользователя, вошедшего в систему на устройстве. Если у учетной записи пользователя есть лицензия Play на приложение, это означает, что он скачал или купил его в Google Play.
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
соответствует ожидаемому, как показано в следующем фрагменте кода:
Котлин
val accountDetails = JSONObject(payload).getJSONObject("accountDetails") val appLicensingVerdict = accountDetails.getString("appLicensingVerdict") if (appLicensingVerdict == "LICENSED") { // Looks good! }
Ява
JSONObject accountDetails = new JSONObject(payload).getJSONObject("accountDetails"); String appLicensingVerdict = accountDetails.getString("appLicensingVerdict"); if (appLicensingVerdict.equals("LICENSED")) { // Looks good! }
Поле сведений о среде
Вы также можете подписаться на дополнительные сигналы об окружающей среде. Риск доступа к приложению сообщает вашему приложению, есть ли запущены другие приложения, которые можно использовать для захвата экрана, отображения наложений или управления устройством. В вердикте Play Protect указывается, включена ли Google Play Protect на устройстве и обнаружено ли на нем известное вредоносное ПО.
Если вы согласились на вердикт «Риск доступа к приложениям» или вердикт «Play Protect» в консоли Google Play, то ваш ответ API будет включать поле environmentDetails
. Поле environmentDetails
может содержать два значения: appAccessRiskVerdict
и playProtectVerdict
.
Вердикт о риске доступа к приложению
После включения поле environmentDetails
в полезных данных Play Integrity API будет содержать новый вердикт о риске доступа к приложению.
{
requestDetails: { ... }
appIntegrity: { ... }
deviceIntegrity: { ... }
accountDetails: { ... }
environmentDetails: {
appAccessRiskVerdict: {
// This field contains one or more responses, for example the following.
appsDetected: ["KNOWN_INSTALLED", "UNKNOWN_INSTALLED", "UNKNOWN_CAPTURING"]
}
}
}
Если риск доступа к приложению был оценен, appAccessRiskVerdict
содержит поле appsDetected
с одним или несколькими ответами. Эти ответы попадают в одну из следующих двух групп в зависимости от источника установки обнаруженных приложений:
Play или системные приложения : приложения, которые установлены Google Play или предварительно загружены производителем устройства в системный раздел устройства (обозначается
FLAG_SYSTEM
). Ответы для таких приложений имеют префиксKNOWN_
.Другие приложения : приложения, которые не установлены из Google Play. Сюда не входят приложения, предварительно загруженные в системный раздел производителем устройства. Ответы для таких приложений имеют префикс
UNKNOWN_
.
Могут быть возвращены следующие ответы:
-
KNOWN_INSTALLED
,UNKNOWN_INSTALLED
- Установлены приложения, соответствующие соответствующему источнику установки.
-
KNOWN_CAPTURING
,UNKNOWN_CAPTURING
- Существуют запущенные приложения, у которых включены разрешения, которые можно использовать для просмотра экрана во время работы вашего приложения. Это исключает любые проверенные службы специальных возможностей, известные Google Play, работающие на устройстве.
-
KNOWN_CONTROLLING
,UNKNOWN_CONTROLLING
- Существуют запущенные приложения, у которых включены разрешения, которые можно использовать для управления устройством и непосредственного управления входными данными в ваше приложение, а также для захвата входных и выходных данных вашего приложения. Это исключает любые проверенные службы специальных возможностей, известные Google Play, работающие на устройстве.
-
KNOWN_OVERLAYS
,UNKNOWN_OVERLAYS
- Существуют запущенные приложения, у которых включены разрешения, которые можно использовать для отображения наложений в вашем приложении. Это исключает любые проверенные службы специальных возможностей, известные Google Play, работающие на устройстве.
- Пусто (пустое значение)
Риск доступа к приложению не оценивается, если необходимое требование было пропущено. В этом случае поле
appAccessRiskVerdict
пусто. Это может произойти по нескольким причинам, включая следующие:- Устройство не заслуживает доверия.
- Форм-фактор устройства не является телефоном, планшетом или складным устройством.
- Устройство не работает под управлением Android 6 (уровень API 23) или выше.
- Версия вашего приложения, установленного на устройстве, неизвестна Google Play.
- Версия Google Play Store на устройстве устарела.
- Только игры : у учетной записи пользователя нет лицензии Play на игру.
- Использовался стандартный запрос с параметром
verdictOptOut
. - Стандартный запрос использовался с версией библиотеки Play Integrity API, которая еще не поддерживает риск доступа к приложениям для стандартных запросов.
Риск доступа к приложениям автоматически исключает проверенные службы специальных возможностей, прошедшие расширенную проверку доступности Google Play (установленные любым магазином приложений на устройстве). «Исключено» означает, что проверенные службы специальных возможностей, работающие на устройстве, не будут возвращать ответ о захвате, контроле или наложении в вердикте о риске доступа к приложению. Чтобы запросить расширенную проверку доступности вашего приложения в Google Play, опубликуйте его в Google Play и убедитесь, что в манифесте вашего приложения для флага isAccessibilityTool
установлено значение true, или запросите проверку .
В следующей таблице приведены некоторые примеры вердиктов и их значение (в этой таблице не перечислены все возможные результаты):
Пример ответа на вердикт о риске доступа к приложению | Интерпретация |
---|---|
appsDetected: ["KNOWN_INSTALLED"] | Установлены только приложения, которые распознаются Google Play или предварительно загружены в системный раздел производителем устройства. Нет запущенных приложений, которые могли бы привести к захвату, контролю или наложению вердиктов. |
appsDetected: ["KNOWN_INSTALLED", "UNKNOWN_INSTALLED", "UNKNOWN_CAPTURING"] | Есть приложения, установленные из Google Play или предварительно загруженные в системный раздел производителем устройства. Есть другие запущенные приложения, и у них включены разрешения, которые можно использовать для просмотра экрана или захвата других входных и выходных данных. |
appsDetected: ["KNOWN_INSTALLED", "KNOWN_CAPTURING", "UNKNOWN_INSTALLED", "UNKNOWN_CONTROLLING"] | Есть Play или запущенная система, у которой включены разрешения, которые можно использовать для просмотра экрана или захвата других входных и выходных данных. Существуют также другие запущенные приложения, у которых включены разрешения, которые можно использовать для управления устройством и непосредственного управления входными данными в ваше приложение. |
appAccessRiskVerdict: {} | Риск доступа к приложению не оценивается, поскольку было пропущено необходимое требование. Например, устройство не было достаточно надежным. |
В зависимости от уровня риска вы можете решить, какая комбинация вердиктов приемлема для продолжения и по каким вердиктам вы хотите принять меры. Следующий фрагмент кода иллюстрирует пример проверки отсутствия запущенных приложений, которые могли бы захватывать экран или управлять вашим приложением:
Котлин
val environmentDetails = JSONObject(payload).getJSONObject("environmentDetails") val appAccessRiskVerdict = environmentDetails.getJSONObject("appAccessRiskVerdict") if (appAccessRiskVerdict.has("appsDetected")) { val appsDetected = appAccessRiskVerdict.getJSONArray("appsDetected").toString() if (!appsDetected.contains("CAPTURING") && !appsDetected.contains("CONTROLLING")) { // Looks good! } }
Ява
JSONObject environmentDetails = new JSONObject(payload).getJSONObject("environmentDetails"); JSONObject appAccessRiskVerdict = environmentDetails.getJSONObject("appAccessRiskVerdict"); if (appAccessRiskVerdict.has("appsDetected")) { String appsDetected = appAccessRiskVerdict.getJSONArray("appsDetected").toString() if (!appsDetected.contains("CAPTURING") && !appsDetected.contains("CONTROLLING")) { // Looks good! } }
Устранение вердиктов о рисках доступа к приложениям
В зависимости от уровня риска вы можете решить, какие вердикты о риске доступа к приложению вы хотите принять, прежде чем позволить пользователю выполнить запрос или действие. Существуют дополнительные подсказки Google Play, которые вы можете показать пользователю после проверки вердикта о риске доступа к приложению. Вы можете указать CLOSE_UNKNOWN_ACCESS_RISK , чтобы попросить пользователя закрыть неизвестные приложения, что приведет к вынесению вердикта о риске доступа к приложению, или вы можете показать CLOSE_ALL_ACCESS_RISK , чтобы попросить пользователя закрыть все приложения (известные и неизвестные), вызывающие вердикт о риске доступа к приложению.
Вердикт Play Protect
После включения поле environmentDetails
в полезных данных Play Integrity API будет содержать вердикт Play Protect:
environmentDetails: {
playProtectVerdict: "NO_ISSUES"
}
playProtectVerdict
может иметь одно из следующих значений:
-
NO_ISSUES
- Play Protect включен, и на устройстве не обнаружено проблем с приложениями.
-
NO_DATA
- Play Protect включен, но сканирование еще не выполнено. Возможно, устройство или приложение Play Store недавно были сброшены.
-
POSSIBLE_RISK
- Play Protect отключен.
-
MEDIUM_RISK
- Play Protect включен и обнаружил, что на устройстве установлены потенциально опасные приложения.
-
HIGH_RISK
- Play Protect включен и обнаружил, что на устройстве установлены опасные приложения.
-
UNEVALUATED
Вердикт Play Protect не был оценен.
Это может произойти по нескольким причинам, включая следующие:
- Устройство не заслуживает доверия.
- Только игры : у учетной записи пользователя нет лицензии Play на игру.
Руководство по использованию вердикта Play Protect
Внутренний сервер вашего приложения может решить, как действовать, основываясь на вердикте, основанном на вашей толерантности к риску. Вот несколько предложений и возможных действий пользователя:
-
NO_ISSUES
- Play Protect включен и не обнаружил никаких проблем, поэтому никаких действий со стороны пользователя не требуется.
-
POSSIBLE_RISK
иNO_DATA
- При получении этих вердиктов попросите пользователя убедиться, что Play Protect включен и выполнил сканирование.
NO_DATA
должен появляться только в редких случаях. -
MEDIUM_RISK
иHIGH_RISK
- В зависимости от вашей толерантности к риску вы можете попросить пользователя запустить Play Protect и принять меры по предупреждениям Play Protect. Если пользователь не может выполнить эти требования, вы можете заблокировать его от действия сервера.