Kết quả về tính toàn vẹn

Trang này mô tả cách diễn giải và xử lý kết quả về tính toàn vẹn được trả về. Cho dù bạn thực hiện yêu cầu API thông thường hay yêu cầu API kiểu cũ, kết quả về tính toàn vẹn sẽ được trả về ở cùng định dạng với nội dung tương tự. Kết quả về tính toàn vẹn sẽ cho biết thông tin về tính hợp lệ của các thiết bị, ứng dụng và tài khoản. Máy chủ của ứng dụng có thể dùng tải trọng phát sinh trong một kết quả đã được giải mã và xác minh nhằm xác định cách tốt nhất để xử lý một hành động hoặc yêu cầu cụ thể trong ứng dụng của bạn.

Định dạng của kết quả về tính toàn vẹn được trả về

Tải trọng này là tệp JSON văn bản thuần tuý chứa các tín hiệu về tính toàn vẹn cùng với thông tin do nhà phát triển cung cấp.

Tải trọng có cấu trúc tổng quát như sau:

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

Trước tiên, bạn phải kiểm tra để đảm bảo rằng các giá trị trong trường requestDetails khớp với các giá trị của yêu cầu ban đầu trước khi kiểm tra từng kết quả về tính toàn vẹn. Các phần sau đây sẽ mô tả chi tiết hơn về từng trường.

Trường thông tin chi tiết về yêu cầu

Trường requestDetails chứa thông tin về yêu cầu, bao gồm thông tin do nhà phát triển cung cấp trong requestHash đối với các yêu cầu thông thường và nonce đối với các yêu cầu kiểu cũ.

Đối với các yêu cầu API thông thường:

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

Các giá trị này phải khớp với các giá trị trong yêu cầu ban đầu. Do đó, hãy xác minh phần requestDetails trong tải trọng JSON bằng cách đảm bảo requestPackageNamerequestHash khớp với nội dung đã gửi trong yêu cầu ban đầu như được minh hoạ trong đoạn mã sau:

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

Đối với các yêu cầu API kiểu cũ:

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

Các giá trị này phải khớp với các giá trị trong yêu cầu ban đầu. Do đó, hãy xác minh phần requestDetails trong tải trọng JSON bằng cách đảm bảo requestPackageNamenonce khớp với nội dung đã gửi trong yêu cầu ban đầu như được minh hoạ trong đoạn mã sau:

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

Trường tính toàn vẹn của ứng dụng

Trường appIntegrity chứa thông tin liên quan đến gói.

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.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  certificateSha256Digest: ["6a6a1474b5cbbb2b1aa57e0bc3"]
  // The version of the app.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  versionCode: "42"
}

appRecognitionVerdict có thể có các giá trị sau:

PLAY_RECOGNIZED
Ứng dụng và chứng chỉ khớp với các phiên bản do Google Play phân phối.
UNRECOGNIZED_VERSION
Chứng chỉ hoặc tên gói không khớp với bản ghi của Google Play.
UNEVALUATED
Tính toàn vẹn của ứng dụng chưa được đánh giá. Thiếu một yêu cầu cần thiết, chẳng hạn như thiết bị không đủ tin cậy.

Để đảm bảo rằng mã thông báo được tạo bởi một ứng dụng mà bạn tạo ra, hãy xác minh rằng tính toàn vẹn của ứng dụng đúng với dự kiến như trong đoạn mã sau:

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("appIntegrity")
val appRecognitionVerdict = requestDetails.getString("appRecognitionVerdict")

if (appRecognitionVerdict == "PLAY_RECOGNIZED") {
    // Looks good!
}

Java

JSONObject requestDetails =
    new JSONObject(payload).getJSONObject("appIntegrity");
String appRecognitionVerdict =
    requestDetails.getString("appRecognitionVerdict");

if (appRecognitionVerdict.equals("PLAY_RECOGNIZED")) {
    // Looks good!
}

Bạn cũng có thể kiểm tra tên gói, phiên bản và chứng chỉ của ứng dụng theo cách thủ công.

Trường tính toàn vẹn của thiết bị

Trường deviceIntegrity có thể chứa một giá trị deviceRecognitionVerdict duy nhất. Giá trị này có một hoặc nhiều nhãn thể hiện mức độ hiệu quả của thiết bị trong việc thực thi tính toàn vẹn của ứng dụng. Nếu thiết bị không đáp ứng tiêu chí của nhãn nào, thì trường deviceIntegrity sẽ trống.

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

Theo mặc định, deviceRecognitionVerdict có thể có một trong các nhãn sau:

MEETS_DEVICE_INTEGRITY
Ứng dụng này đang chạy trên một thiết bị Android sử dụng dịch vụ Google Play. Thiết bị vượt qua quy trình kiểm tra tính toàn vẹn của hệ thống và đáp ứng các yêu cầu về khả năng tương thích với Android.
Không có nhãn (giá trị trống)
Ứng dụng đang chạy trên một thiết bị có dấu hiệu bị tấn công (chẳng hạn như hook API) hoặc bị xâm phạm hệ thống (chẳng hạn như đã bị can thiệp vào hệ thống) hoặc ứng dụng không chạy trên thiết bị thực tế (chẳng hạn như trình mô phỏng không vượt qua được quy trình kiểm tra tính toàn vẹn của Google Play).

Để đảm bảo rằng mã thông báo đến từ một thiết bị đáng tin cậy, hãy xác minh deviceRecognitionVerdict theo dự kiến như trong đoạn mã sau:

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

Nếu bạn gặp sự cố liên quan đến tính toàn vẹn của thiết bị đang kiểm tra, hãy đảm bảo bạn đã cài đặt ROM gốc (ví dụ: bằng cách đặt lại thiết bị) và đã khoá trình tải khởi động. Bạn cũng có thể tạo phép kiểm thử API Tính toàn vẹn của Play trong Play Console.

Nếu bạn chọn nhận các nhãn bổ sung trong kết quả về tính toàn vẹn, thì deviceRecognitionVerdict có thể có các nhãn bổ sung sau:

MEETS_BASIC_INTEGRITY
Ứng dụng này đang chạy trên một thiết bị đã vượt qua quy trình kiểm tra cơ bản về tính toàn vẹn của hệ thống. Có thể thiết bị này không đáp ứng yêu cầu về khả năng tương thích với Android và không được phê duyệt để sử dụng dịch vụ Google Play. Ví dụ: có thể thiết bị này đang chạy một phiên bản Android không xác định, dùng trình tải khởi động đã được mở khoá hoặc chưa được nhà sản xuất chứng nhận.
MEETS_STRONG_INTEGRITY
Ứng dụng này đang chạy trên một thiết bị Android sử dụng dịch vụ Google Play, đồng thời đảm bảo chắc chắn tính toàn vẹn của hệ thống, chẳng hạn như bằng chứng dựa trên phần cứng về tính toàn vẹn khi khởi động. Thiết bị vượt qua quy trình kiểm tra tính toàn vẹn của hệ thống và đáp ứng các yêu cầu về khả năng tương thích với Android.

Ngoài ra, nếu chúng tôi phát hành ứng dụng cho trình mô phỏng được phê duyệt, thì deviceRecognitionVerdict cũng có thể gắn nhãn sau:

MEETS_VIRTUAL_INTEGRITY
Ứng dụng này đang chạy trên một trình mô phỏng Android sử dụng dịch vụ Google Play. Trình mô phỏng này vượt qua quy trình kiểm tra tính toàn vẹn của hệ thống và đáp ứng các yêu cầu chính về khả năng tương thích với Android.

Trường thông tin chi tiết tài khoản

Trường accountDetails chứa một giá trị appLicensingVerdict duy nhất, đại diện cho trạng thái cấp phép/quyền của ứng dụng.

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

appLicensingVerdict có thể có các giá trị sau:

LICENSED
Người dùng có quyền sử dụng ứng dụng. Nói cách khác, người dùng đã cài đặt hoặc mua ứng dụng của bạn trên Google Play.
UNLICENSED
Người dùng không có quyền sử dụng ứng dụng. Điều này xảy ra khi người dùng không cài đặt ứng dụng của bạn qua cửa hàng ứng dụng hoặc có được ứng dụng thông qua một nơi khác không phải là Google Play chẳng hạn.
UNEVALUATED

Thông tin cấp phép chưa được đánh giá vì thiếu một yêu cầu cần thiết.

Điều này có thể xảy ra vì một vài lý do như:

  • Thiết bị không đủ tin cậy.
  • Google Play không xác định được phiên bản ứng dụng trên thiết bị đó.
  • Người dùng chưa đăng nhập vào Google Play.

Để kiểm tra xem người dùng có quyền sử dụng ứng dụng của bạn hay không, hãy xác minh rằng appLicensingVerdict là đúng với dự kiến như trong đoạn mã sau:

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("accountDetails")
val appLicensingVerdict = requestDetails.getString("appLicensingVerdict")

if (appLicensingVerdict == "LICENSED") {
    // Looks good!
}

Java

JSONObject requestDetails =
    new JSONObject(payload).getJSONObject("accountDetails");
String appLicensingVerdict = requestDetails.getString("appLicensingVerdict");

if (appLicensingVerdict.equals("LICENSED")) {
    // Looks good!
}