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

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

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

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

MEETS_DEVICE_INTEGRITY
แอปกำลังทำงานบนอุปกรณ์ Android ของแท้ที่ผ่านการรับรองจาก Play Protect ใน 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 ของอุปกรณ์อาจล็อกหรือปลดล็อกอยู่ และสถานะการบูตอาจได้รับการยืนยันหรือไม่ก็ได้ อุปกรณ์อาจไม่ผ่านการรับรองของ Play Protect ซึ่งในกรณีนี้ Google ไม่สามารถรับประกันความปลอดภัย ความเป็นส่วนตัว หรือการรองรับแอป ใน Android 13 ขึ้นไป MEETS_BASIC_INTEGRITYผลการตัดสินกำหนดให้ต้องมีรูทของความน่าเชื่อถือที่ Google เป็นผู้รับรองเท่านั้น
MEETS_STRONG_INTEGRITY
แอปทำงานในอุปกรณ์ Android ของแท้ที่ผ่านการรับรองจาก Play Protect ซึ่งมีอัปเดตความปลอดภัยล่าสุด
  • ใน 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 หากไม่เป็นไปตามข้อกําหนดที่จําเป็น ในกรณีนี้ จะไม่มีการตั้งค่าในช่อง 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 payload จะมีผลการตัดสินความเสี่ยงในการเข้าถึงแอปใหม่

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