การตัดสินด้านความสมบูรณ์

หน้านี้อธิบายวิธีตีความและใช้งานผลการตัดสินด้านความสมบูรณ์ที่ส่งคืน ไม่ว่าคุณจะส่งคำขอ 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 ตรงกับสิ่งที่ส่งในคำขอ เดิม ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

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

สำหรับคำขอ 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 ตรงกับที่ส่งในคำขอเดิม ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

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ซึ่งมีป้ายกำกับอย่างน้อย 1 รายการที่แสดงถึงความสามารถของอุปกรณ์ ในการบังคับใช้ความสมบูรณ์ของแอป หากอุปกรณ์ไม่เป็นไปตามเกณฑ์ของป้ายกำกับใดๆ ฟิลด์ deviceIntegrity จะไม่แสดง deviceRecognitionVerdict

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

โดยค่าเริ่มต้น deviceRecognitionVerdict อาจมีสิ่งต่อไปนี้

MEETS_DEVICE_INTEGRITY
แอปกำลังทำงานบนอุปกรณ์ Android ของแท้ที่ผ่านการรับรอง ใน Android 13 ขึ้นไป จะมีหลักฐานที่ได้รับการสนับสนุนจากฮาร์ดแวร์ว่า Bootloader ของอุปกรณ์ล็อกอยู่และระบบปฏิบัติการ Android ที่โหลดเป็นอิมเมจของผู้ผลิตอุปกรณ์ที่ได้รับการรับรอง
ว่าง (ค่าว่าง)
แอปกําลังทํางานในอุปกรณ์ที่มีสัญญาณการโจมตี (เช่น การฮุก API) หรือการบุกรุกระบบ (เช่น การรูท) หรือแอปไม่ทํางานในอุปกรณ์จริง (เช่น โปรแกรมจําลองที่ไม่ผ่านการตรวจสอบความน่าเชื่อถือของ 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!
}

หากอุปกรณ์ทดสอบมีปัญหาในการปฏิบัติตามข้อกำหนดด้านความสมบูรณ์ของอุปกรณ์ โปรดตรวจสอบว่าได้ติดตั้ง ROM จากโรงงานแล้ว (เช่น โดยการรีเซ็ตอุปกรณ์) และล็อก Bootloader แล้ว นอกจากนี้ คุณยังสร้างการทดสอบ Play Integrity API ใน Play Console ได้ด้วย

ป้ายกำกับอุปกรณ์แบบมีเงื่อนไข

หากแอปของคุณเผยแพร่ไปยัง Google Play Games สำหรับ PC deviceRecognitionVerdict อาจมีป้ายกำกับต่อไปนี้ด้วย

MEETS_VIRTUAL_INTEGRITY
แอปกำลังทำงานบนโปรแกรมจำลองที่ขับเคลื่อนโดย Android ที่มี บริการ Google Play โปรแกรมจำลองผ่านการตรวจสอบความสมบูรณ์ของระบบและเป็นไปตามข้อกำหนดหลักด้านความเข้ากันได้ของ Android

ข้อมูลอุปกรณ์และการเรียกคืนของอุปกรณ์ (ไม่บังคับ)

คุณเลือกรับป้ายกำกับอุปกรณ์ที่ไม่บังคับได้โดยเป็นส่วนหนึ่งของกลยุทธ์การบังคับใช้ แบบแบ่งระดับ หากเลือกรับป้ายกำกับเพิ่มเติมในการตัดสินความสมบูรณ์ deviceRecognitionVerdict อาจมีป้ายกำกับเพิ่มเติมต่อไปนี้

MEETS_BASIC_INTEGRITY
แอปกำลังทำงานบนอุปกรณ์ที่ผ่านการตรวจสอบความสมบูรณ์ของระบบพื้นฐาน Bootloader ของอุปกรณ์อาจล็อกหรือปลดล็อกได้ และสถานะการเปิดเครื่องอาจได้รับการยืนยันหรือไม่ได้รับการยืนยันก็ได้ อุปกรณ์อาจไม่ได้รับการรับรอง ในกรณีนี้ Google จะไม่สามารถรับประกันความปลอดภัย ความเป็นส่วนตัว หรือความเข้ากันได้ของแอป ใน Android 13 ขึ้นไป MEETS_BASIC_INTEGRITY คำตัดสินกำหนดให้ Google เป็นผู้ระบุ รูทของความน่าเชื่อถือเท่านั้น
MEETS_STRONG_INTEGRITY
แอปทำงานบนอุปกรณ์ Android ของแท้ที่ผ่านการรับรอง ซึ่งมีการอัปเดตความปลอดภัยล่าสุด
  • ใน Android 13 ขึ้นไป MEETS_STRONG_INTEGRITYคำตัดสินต้องมี MEETS_DEVICE_INTEGRITYและการอัปเดตความปลอดภัยในช่วงปีที่ผ่านมาสำหรับพาร์ติชันทั้งหมด ของอุปกรณ์ ซึ่งรวมถึงแพตช์พาร์ติชันระบบปฏิบัติการ Android และ แพตช์พาร์ติชันของผู้ให้บริการ
  • ใน Android 12 ลงไป MEETS_STRONG_INTEGRITYคำตัดสินเท่านั้น ต้องมีหลักฐานที่ใช้ฮาร์ดแวร์รับรองความสมบูรณ์ของการบูตและไม่ กำหนดให้อุปกรณ์ต้องมีการอัปเดตความปลอดภัยล่าสุด ดังนั้น เมื่อใช้ MEETS_STRONG_INTEGRITY เราขอแนะนำให้พิจารณา เวอร์ชัน Android SDK ในช่อง deviceAttributes ด้วย

อุปกรณ์เครื่องเดียวจะแสดงป้ายกำกับอุปกรณ์หลายรายการในผลการตัดสินด้านความสมบูรณ์ของอุปกรณ์ หากตรงตามเกณฑ์ของป้ายกำกับแต่ละรายการ

แอตทริบิวต์อุปกรณ์

นอกจากนี้ คุณยังเลือกใช้แอตทริบิวต์อุปกรณ์ได้ด้วย ซึ่งจะบอกเวอร์ชัน Android SDK ของ ระบบปฏิบัติการ Android ที่ทำงานในอุปกรณ์ เวอร์ชัน Android SDK มีประโยชน์ในการ แยกความแตกต่างระหว่างอุปกรณ์ที่ใช้ Android 13 ขึ้นไปกับอุปกรณ์ที่ ใช้ Android SDK เวอร์ชันต่ำกว่า ซึ่งเป็นส่วนหนึ่งของกลยุทธ์การบังคับใช้แบบแบ่งระดับ ในอนาคต เราอาจขยายการใช้งาน ด้วยแอตทริบิวต์อุปกรณ์อื่นๆ

ค่าเวอร์ชัน SDK คือหมายเลขเวอร์ชัน Android SDK ที่กำหนดไว้ใน Build.VERSION_CODES ระบบจะไม่ประเมิน SDK version หากไม่เป็นไปตามข้อกําหนดที่จําเป็น ในกรณีนี้ ระบบจะไม่ตั้งค่าฟิลด์ sdkVersion ดังนั้นฟิลด์ deviceAttributes จึงว่างเปล่า ซึ่งอาจเกิดขึ้นจากสาเหตุต่อไปนี้

  • อุปกรณ์ไม่น่าเชื่อถือพอ
  • อุปกรณ์มีปัญหาทางเทคนิค

หากเลือกรับ 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.
}

กิจกรรมล่าสุดในอุปกรณ์

นอกจากนี้ คุณยังเลือกใช้กิจกรรมล่าสุดในอุปกรณ์ได้ด้วย ซึ่งจะบอกจำนวนครั้งที่แอปของคุณร้องขอโทเค็นความสมบูรณ์ในอุปกรณ์ที่ระบุในชั่วโมงที่ผ่านมา คุณสามารถใช้กิจกรรมล่าสุดในอุปกรณ์เพื่อปกป้องแอปจากอุปกรณ์ที่ทำงานมากเกินไปโดยไม่คาดคิด ซึ่งอาจเป็นสัญญาณบ่งบอกถึงการโจมตีที่กำลังเกิดขึ้น คุณสามารถตัดสินใจได้ว่าจะเชื่อถือระดับกิจกรรมในอุปกรณ์ล่าสุดแต่ละระดับมากน้อยเพียงใด โดยพิจารณาจากจำนวนครั้งที่คุณคาดหวังให้แอปที่ติดตั้งในอุปกรณ์ทั่วไปร้องขอโทเค็นความสมบูรณ์ในแต่ละชั่วโมง

หากเลือกรับ recentDeviceActivity ฟิลด์ deviceIntegrity จะมีค่า 2 ค่าดังนี้

"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 ระบบไม่ได้ประเมินกิจกรรมล่าสุดในอุปกรณ์ ซึ่งอาจเกิดขึ้นเนื่องจากสาเหตุต่อไปนี้
  • อุปกรณ์ไม่น่าเชื่อถือพอ
  • Google Play ไม่รู้จักเวอร์ชันของแอปที่ติดตั้งในอุปกรณ์
  • ปัญหาทางเทคนิคในอุปกรณ์

การเรียกคืนอุปกรณ์ (เบต้า)

นอกจากนี้ คุณยังเลือกใช้การเรียกคืนข้อมูลอุปกรณ์ได้ด้วย ซึ่งจะช่วยให้คุณจัดเก็บข้อมูลที่กำหนดเองต่ออุปกรณ์บางอย่างไว้กับอุปกรณ์ที่เฉพาะเจาะจง และเรียกข้อมูลได้อย่างน่าเชื่อถือเมื่อติดตั้งแอปอีกครั้งในภายหลังบนอุปกรณ์เครื่องเดียวกัน หลังจากขอโทเค็นความสมบูรณ์แล้ว คุณจะทำการเรียกแบบเซิร์ฟเวอร์ต่อเซิร์ฟเวอร์แยกต่างหาก เพื่อแก้ไขค่าการเรียกคืนของอุปกรณ์สำหรับอุปกรณ์ที่เฉพาะเจาะจง

หากเลือกใช้ deviceRecall ช่อง deviceIntegrity จะมีข้อมูลการเรียกคืนของอุปกรณ์ที่คุณตั้งค่าไว้สำหรับอุปกรณ์ที่เฉพาะเจาะจง

"deviceIntegrity": {
  "deviceRecognitionVerdict": ["MEETS_DEVICE_INTEGRITY"],
  "deviceRecall": {
    "values": {
      "bitFirst": true,
      "bitSecond": false,
      "bitThird": true
    },
    "writeDates": {
      // Write time in YYYYMM format in UTC.
      "yyyymmFirst": 202401,
      // Note that yyyymmSecond is not set because bitSecond is false.
      "yyyymmThird": 202310
    }
  }
}

deviceRecall จะแบ่งออกเป็น 2 ช่อง ดังนี้

  • values: เรียกค่าบิตที่คุณตั้งค่าไว้ก่อนหน้านี้สำหรับอุปกรณ์นี้
  • writeDates: จดจำวันที่เขียนบิตใน UTC ให้ถูกต้องตามปีและ เดือน ระบบจะอัปเดตวันที่เขียนของบิตการเรียกคืนทุกครั้งที่ตั้งค่าบิตเป็น true และจะนำออกเมื่อตั้งค่าบิตเป็น false

ในกรณีที่ไม่มีข้อมูลการเรียกคืนของอุปกรณ์ ค่าการเรียกคืนของอุปกรณ์ จะเป็นค่าว่าง

"deviceIntegrity": {
  "deviceRecognitionVerdict": ["MEETS_DEVICE_INTEGRITY"],
  "deviceRecall": {
    "values": {},
    "writeDates": {}
  }
}

ฟิลด์รายละเอียดบัญชี

ฟิลด์ 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 เป็นไปตามที่คาดไว้ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

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 Protect จะบอกว่า Google Play Protect เปิดใช้อยู่ในอุปกรณ์หรือไม่ และ ตรวจพบมัลแวร์ที่รู้จักหรือไม่

หากคุณเลือกรับผลการตัดสินความเสี่ยงในการเข้าถึงแอปหรือผลการตัดสิน Play Protect ใน Google Play Console การตอบกลับจาก API จะมีฟิลด์ environmentDetails ฟิลด์ environmentDetails มีค่าได้ 2 ค่า ได้แก่ 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 ที่มีการตอบกลับอย่างน้อย 1 รายการ การตอบกลับเหล่านี้จะอยู่ในกลุ่มใดกลุ่มหนึ่งต่อไปนี้ โดยขึ้นอยู่กับแหล่งที่มาของการติดตั้งแอปที่ตรวจพบ

  • แอป 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 ที่ได้รับการปรับปรุงแล้ว (ติดตั้งโดย App Store ใดก็ได้ในอุปกรณ์) "ยกเว้น" หมายความว่าบริการช่วยเหลือพิเศษที่ยืนยันแล้ว ซึ่งทำงานบนอุปกรณ์จะไม่แสดงผลการตอบกลับการจับภาพ การควบคุม หรือ การซ้อนทับในผลการตัดสินความเสี่ยงในการเข้าถึงแอป หากต้องการขอรับการตรวจสอบการช่วยเหลือพิเศษของ Google Play ที่เพิ่มประสิทธิภาพสำหรับแอปการช่วยเหลือพิเศษ ให้เผยแพร่แอปใน Google Play โดยตรวจสอบว่าแอปมีisAccessibilityToolตั้งค่าเป็น true ใน ไฟล์ Manifest ของแอป หรือขอรับการตรวจสอบ

ตัวอย่างคำตัดสินความเสี่ยงในการเข้าถึงแอป

ตารางต่อไปนี้แสดงตัวอย่างผลการตัดสินความเสี่ยงในการเข้าถึงแอปและ ความหมายของผลการตัดสิน (ตารางนี้ไม่ได้แสดงผลลัพธ์ที่เป็นไปได้ทั้งหมด)

ตัวอย่างการตอบกลับคำตัดสินความเสี่ยงในการเข้าถึงแอป การตีความ
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: {} ระบบไม่ได้ประเมินความเสี่ยงในการเข้าถึงแอป เนื่องจากไม่เป็นไปตามข้อกําหนดที่จําเป็น เช่น อุปกรณ์ไม่น่าเชื่อถือพอ

คุณสามารถตัดสินใจได้ว่าคำตัดสินใดที่ยอมรับได้เพื่อดำเนินการต่อและคำตัดสินใดที่ต้องการดำเนินการโดยขึ้นอยู่กับระดับความเสี่ยง ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการยืนยันว่าไม่มี แอปที่ทำงานอยู่ซึ่งอาจจับภาพหน้าจอหรือควบคุมแอปของคุณได้

Kotlin

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

Java

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 ได้ ทั้งนี้ขึ้นอยู่กับความเสี่ยงที่คุณยอมรับได้ หากผู้ใช้ไม่สามารถปฏิบัติตามข้อกำหนดเหล่านี้ คุณสามารถบล็อกไม่ให้ผู้ใช้ดำเนินการในเซิร์ฟเวอร์ได้