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

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

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