Bütünlük kararları

Bu sayfada, döndürülen bütünlük kararının nasıl yorumlanacağı ve bu kararla nasıl çalışılacağı açıklanmaktadır. İster standart ister klasik bir API isteğinde bulunun, bütünlük kararı benzer içerikle aynı biçimde döndürülür. Bütünlük kararı; cihazların, uygulamaların ve hesapların geçerliliği hakkında bilgi iletir. Uygulamanızın sunucusu, uygulamanızdaki belirli bir işlem veya isteği en iyi şekilde nasıl gerçekleştireceğinizi belirlemek için bu yükü, şifresi çözülmüş ve doğrulanmış bir kararda kullanabilir.

Entegrasyon kararı biçimi döndürüldü

Yük, düz metin JSON biçimindedir ve geliştirici tarafından sağlanan bilgilerin yanı sıra bütünlük sinyallerini içerir.

Genel yük yapısı şu şekildedir:

{
  requestDetails: { ... }
  appIntegrity: { ... }
  deviceIntegrity: { ... }
  accountDetails: { ... }
  environmentDetails: { ... }
}

Her bir entegrasyon kararını kontrol etmeden önce requestDetails alanındaki değerlerin orijinal isteğin değerleriyle eşleşip eşleşmediğini kontrol etmeniz gerekir. Aşağıdaki bölümlerde her alan daha ayrıntılı olarak açıklanmaktadır.

İstek ayrıntıları alanı

requestDetails alanı, standart istekler için requestHash içinde geliştirici tarafından sağlanan bilgiler ve klasik istekler için nonce dahil olmak üzere istekle ilgili bilgileri içerir.

Standart API istekleri için:

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"
}

Bu değerler, orijinal isteğin değerleriyle eşleşmelidir. Bu nedenle, aşağıdaki kod snippet'inde gösterildiği gibi requestPackageName ve requestHash değerlerinin orijinal istekte gönderilenle eşleştiğinden emin olarak JSON yükünün requestDetails kısmını doğrulayın:

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.
        ...
}

Klasik API istekleri için:

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"
}

Bu değerler, orijinal isteğin değerleriyle eşleşmelidir. Bu nedenle, aşağıdaki kod snippet'inde gösterildiği gibi requestPackageName ve nonce değerlerinin orijinal istekte gönderilenle eşleştiğinden emin olarak JSON yükünün requestDetails kısmını doğrulayın:

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.
        ...
}

Uygulama bütünlüğü alanı

appIntegrity alanı, paketle ilgili bilgileri içerir.

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 aşağıdaki değerlere sahip olabilir:

PLAY_RECOGNIZED
Uygulama ve sertifika, Google Play tarafından dağıtılan sürümlerle eşleşiyor.
UNRECOGNIZED_VERSION
Sertifika veya paket adı Google Play kayıtlarıyla eşleşmiyor.
UNEVALUATED
Uygulama bütünlüğü değerlendirilmedi. Gerekli bir şart karşılanmamıştır (örneğin, cihaz yeterince güvenilir değildir).

Jetonun sizin tarafınızdan oluşturulan bir uygulama tarafından oluşturulduğundan emin olmak için aşağıdaki kod snippet'inde gösterildiği gibi uygulama bütünlüğünün beklendiği gibi olduğunu doğrulayın:

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!
}

Uygulama paketi adını, uygulama sürümünü ve uygulama sertifikalarını manuel olarak da kontrol edebilirsiniz.

Cihaz bütünlüğü alanı

deviceIntegrity alanı, cihazın uygulama bütünlüğünü ne kadar iyi zorunlu kılabileceğini temsil eden bir veya daha fazla etikete sahip tek bir değer (deviceRecognitionVerdict) içerebilir. Cihaz, hiçbir etiketin kriterlerini karşılamıyorsa deviceIntegrity alanı boş olur.

deviceIntegrity: {
  // "MEETS_DEVICE_INTEGRITY" is one of several possible values.
  deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"]
}

Varsayılan olarak deviceRecognitionVerdict aşağıdakileri içerebilir:

MEETS_DEVICE_INTEGRITY
Uygulama, Google Play Hizmetleri'nin yüklü olduğu Android destekli bir cihazda çalışıyor. Cihaz, sistem bütünlüğü kontrollerini geçer ve Android uyumluluk şartlarını karşılar.
Boş (boş değer)
Uygulama, saldırı belirtileri (ör. API kancalama) veya sistemde güvenlik ihlali (rootlanma gibi) belirtileri olan bir cihazda çalışıyor ya da uygulama fiziksel bir cihazda çalışmıyor (ör. Google Play bütünlük kontrollerini geçemeyen bir emülatörde).

Jetonun güvenilir bir cihazdan geldiğinden emin olmak için deviceRecognitionVerdict öğesinin aşağıdaki kod snippet'inde gösterildiği gibi beklendiği gibi olduğunu doğrulayın:

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!
}

Test cihazınızın cihaz bütünlüğünü karşılamasıyla ilgili sorunlar yaşıyorsanız fabrika ROM'unun yüklü olduğundan (örneğin, cihazı sıfırlayarak) ve bootloader'ın kilitli olduğundan emin olun. Play Console'da Play Integrity API testlerini de oluşturabilirsiniz.

Koşullu cihaz etiketleri

Uygulamanız PC Üzerinde Google Play Games'de yayınlanıyorsa deviceRecognitionVerdict üzerinde aşağıdaki etiket de bulunabilir:

MEETS_VIRTUAL_INTEGRITY
Uygulama, Google Play Hizmetleri ile Android destekli bir emülatörde çalışıyor. Emülatör, sistem bütünlüğü kontrollerini geçer ve temel Android uyumluluk gereksinimlerini karşılar.

İsteğe bağlı cihaz bilgileri

Entegrasyon kararında ek etiketler almayı etkinleştirirseniz deviceRecognitionVerdict aşağıdaki ek etiketleri içerebilir:

MEETS_BASIC_INTEGRITY
Uygulama, temel sistem bütünlüğü kontrollerini geçen bir cihazda çalışıyor. Cihaz, Android uyumluluk koşullarını karşılamıyor olabilir ve Google Play hizmetlerini çalıştırmak üzere onaylanmayabilir. Örneğin, cihaz Android'in tanınmayan bir sürümünü çalıştırıyor, kilidi açılmış bir bootloader içeriyor veya üretici tarafından onaylanmamış olabilir.
MEETS_STRONG_INTEGRITY
Uygulama, Google Play Hizmetleri'nin bulunduğu Android destekli bir cihazda çalışmaktadır ve sistem bütünlüğü, donanım bütünlüğü destekli kanıtı gibi güçlü bir sistem bütünlüğü garantisine sahiptir. Cihaz, sistem bütünlüğü kontrollerini geçer ve Android uyumluluk koşullarını karşılar.

Etiketin ölçütlerinden her biri karşılanırsa tek bir cihaz, cihaz bütünlüğü kararında birden fazla cihaz etiketi döndürür.

Son cihaz etkinliği (Beta)

Son cihaz etkinliğini de etkinleştirebilirsiniz. Bu etkinlik, uygulamanızın son bir saat içinde belirli bir cihazda kaç kez bütünlük jetonu istediğini belirtir. Uygulamanızı, etkin bir saldırının göstergesi olabilecek beklenmedik, hiperaktif cihazlara karşı korumak için son cihaz etkinliğini kullanabilirsiniz. Uygulamanızın her saat kaç kez bütünlük jetonu istemesini beklediğinize bağlı olarak son cihaz etkinliği düzeylerine ne kadar güveneceğinize karar verebilirsiniz.

recentDeviceActivity verisini almayı etkinleştirirseniz deviceIntegrity alanında iki değer bulunur:

deviceIntegrity: {
  deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"]
  recentDeviceActivity: {
    // "LEVEL_2" is one of several possible values.
    deviceActivityLevel: "LEVEL_2"
  }
}

deviceActivityLevel tanımları modlar arasında farklılık gösterir ve aşağıdaki değerlerden birine sahip olabilir:

Son cihaz etkinliği düzeyi Standart API isteği Klasik API isteği
LEVEL_1 (en düşük) Uygulama, son bir saat içinde bu cihazda 10 veya daha az bütünlük jetonu istedi. Uygulama, son bir saat içinde bu cihazda 5 veya daha az bütünlük jetonu istedi.
LEVEL_2 Uygulama, son bir saat içinde bu cihazda 11 ile 25 arasında bütünlük jetonu istedi. Uygulama, son bir saat içinde bu cihazda 6 ila 15 bütünlük jetonu istedi.
LEVEL_3 Uygulama, son bir saat içinde bu cihazda 26 ila 50 bütünlük jetonu istedi. Uygulama, son bir saat içinde bu cihazda 16 ila 30 bütünlük jetonu istedi.
LEVEL_4 (en yüksek) Uygulama son bir saat içinde bu cihazda 50'den fazla bütünlük jetonu istedi. Uygulama son bir saat içinde bu cihazda 30'dan fazla bütünlük jetonu istedi.
UNEVALUATED Son cihaz etkinliği değerlendirilmedi.

Bu durum aşağıdakiler dahil olmak üzere çeşitli nedenlerden kaynaklanabilir:

  • Cihaz yeterince güvenilir değildir.
  • Cihazda uygulamanızın Google Play tarafından tanınmayan bir sürümü yüklüdür.
  • Cihazdaki teknik sorunlar.

Hesap ayrıntıları alanı

accountDetails alanı, uygulama lisanslama durumunu temsil eden tek bir appLicensingVerdict değeri içerir.

accountDetails: {
  // This field can be LICENSED, UNLICENSED, or UNEVALUATED.
  appLicensingVerdict: "LICENSED"
}

appLicensingVerdict aşağıdaki değerlerden birine sahip olabilir:

LICENSED
Kullanıcının uygulamadan yararlanma hakkı var. Başka bir deyişle, kullanıcı, uygulamanızı Google Play'den yüklemiş veya satın almıştır.
UNLICENSED
Kullanıcının uygulamadan yararlanma hakkı yok. Bu durum, örneğin, kullanıcı uygulamanızı başka cihazdan yüklediğinde veya Google Play'den edinmediğinde meydana gelir. Bu sorunu düzeltmek için kullanıcılara GET_LICENSED iletişim kutusunu gösterebilirsiniz.
UNEVALUATED

Gerekli bir koşul karşılanmadığı için lisanslama ayrıntıları değerlendirilmemiştir.

Bu durum aşağıdakiler dahil olmak üzere çeşitli nedenlerden kaynaklanabilir:

  • Cihaz yeterince güvenilir değildir.
  • Cihazda uygulamanızın Google Play tarafından tanınmayan bir sürümü yüklüdür.
  • Kullanıcı Google Play'de oturum açmamıştır.

Kullanıcının uygulamanızla ilgili uygulamadan yararlanma hakkına sahip olup olmadığını kontrol etmek için appLicensingVerdict aşağıdaki kod snippet'inde gösterildiği gibi beklendiği gibi olduğunu doğrulayın:

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!
}

Ortam ayrıntıları alanı

Google Play Console'da Play Protect kararını etkinleştirdiyseniz API yanıtınızda environmentDetails alanı yer alır. environmentDetails alanı, cihazda Google Play Protect hakkında bilgi sağlayan tek bir değer (playProtectVerdict) içerir.

environmentDetails: {
  playProtectVerdict: "NO_ISSUES"
}

playProtectVerdict aşağıdaki değerlerden birine sahip olabilir:

NO_ISSUES
Play Protect açıldı ve cihazda uygulama sorunu bulamadı.
NO_DATA
Play Protect açıldı ancak henüz tarama yapılmadı. Cihaz veya Play Store uygulaması kısa süre önce sıfırlanmış olabilir.
POSSIBLE_RISK
Play Protect kapalı.
MEDIUM_RISK
Play Protect açık ve cihazda zararlı olabilecek uygulamalar buldu.
HIGH_RISK
Play Protect açıldı ve cihazda yüklü tehlikeli uygulamalar buldu.
UNEVALUATED

Play Protect kararı değerlendirilmedi.

Bu durum aşağıdakiler dahil olmak üzere çeşitli nedenlerden kaynaklanabilir:

  • Cihaz yeterince güvenilir değildir.
  • Yalnızca oyunlar: Kullanıcı hesabı LICENSED değil.

Play Protect kararının nasıl kullanılacağıyla ilgili rehberlik

Uygulamanızın arka uç sunucusu, risk toleransınıza bağlı olarak karara göre nasıl hareket edeceğine karar verebilir. Aşağıda bazı öneriler ve olası kullanıcı işlemleri verilmiştir:

NO_ISSUES
Play Protect açık olduğu için herhangi bir sorun bulmadığı için kullanıcının işlem yapmasına gerek yok.
POSSIBLE_RISK ve NO_DATA
Bu kararları alırken kullanıcıdan Play Protect'in açık olduğundan ve tarama gerçekleştirdiğinden emin olmasını isteyin. NO_DATA yalnızca nadir durumlarda görünmelidir.
MEDIUM_RISK ve HIGH_RISK
Risk toleransınıza bağlı olarak kullanıcıdan Play Protect'i başlatmasını ve Play Protect uyarılarıyla ilgili işlem yapmasını isteyebilirsiniz. Kullanıcı bu gereksinimleri karşılayamazsa sunucu işleminde onu engelleyebilirsiniz.