Đưa ra yêu cầu API thông thường

Trang này mô tả việc đưa ra các yêu cầu API thông thường cho kết quả về tính toàn vẹn. Hành động yêu cầu này được hỗ trợ trên Android 5.0 (API cấp 21) trở lên. Bạn có thể đưa ra một yêu cầu API thông thường cho kết quả về tính toàn vẹn bất cứ khi nào ứng dụng của bạn thực hiện lệnh gọi tới máy chủ để kiểm tra xem lượt tương tác đó có thật hay không.

Tổng quan

Sơ đồ trình tự thể hiện thiết kế cấp cao của API Tính toàn vẹn của Play

Một yêu cầu thông thường bao gồm 2 phần:

  • Chuẩn bị trình cung cấp mã thông báo về tính toàn vẹn (một lần): Bạn cần gọi API Tính toàn vẹn để chuẩn bị kỹ trình cung cấp mã thông báo về tính toàn vẹn trước khi cần lấy kết quả về tính toàn vẹn. Ví dụ: bạn có thể thực hiện việc này khi ứng dụng khởi chạy hoặc ở chế độ nền trước khi cần kết quả về tính toàn vẹn.
  • Yêu cầu mã thông báo về tính toàn vẹn (theo yêu cầu): Bất cứ khi nào ứng dụng của bạn tạo một yêu cầu đến máy chủ mà bạn muốn kiểm tra để đảm bảo yêu cầu đó là thật, bạn sẽ yêu cầu một mã thông báo về tính toàn vẹn và gửi mã này đến máy chủ phụ trợ của ứng dụng để giải mã và xác minh. Sau đó, máy chủ phụ trợ của bạn có thể quyết định cách xử lý.

Chuẩn bị trình cung cấp mã thông báo về tính toàn vẹn (một lần):

  1. Ứng dụng của bạn gọi trình cung cấp mã thông báo về tính toàn vẹn bằng số dự án trên đám mây của Google.
  2. Ứng dụng của bạn lưu giữ trình cung cấp mã thông báo về tính toàn vẹn trong bộ nhớ để thực hiện thêm các lệnh gọi kiểm tra chứng thực.

Yêu cầu mã thông báo về tính toàn vẹn (theo yêu cầu):

  1. Đối với hành động của người dùng cần được bảo vệ, ứng dụng sẽ tính toán hàm băm (sử dụng thuật toán băm bất kỳ phù hợp, chẳng hạn như SHA256) của yêu cầu cần thực hiện.
  2. Ứng dụng yêu cầu mã thông báo về tính toàn vẹn, chuyển hàm băm yêu cầu.
  3. Ứng dụng sẽ nhận được mã thông báo về tính toàn vẹn đã ký và mã hoá từ API Tính toàn vẹn của Play.
  4. Ứng dụng chuyển mã thông báo về tính toàn vẹn đến máy chủ phụ trợ của ứng dụng.
  5. Máy chủ phụ trợ của ứng dụng sẽ gửi mã thông báo này đến máy chủ của Google Play. Máy chủ của Google Play giải mã và xác minh kết quả, trả về kết quả cho máy chủ phụ trợ của ứng dụng.
  6. Máy chủ phụ trợ của ứng dụng sẽ quyết định cách xử lý, dựa trên các tín hiệu có trong tải trọng của mã thông báo.
  7. Máy chủ phụ trợ của ứng dụng sẽ gửi kết quả về quyết định cho ứng dụng.

Chuẩn bị trình cung cấp mã thông báo về tính toàn vẹn (một lần)

Trước khi thực hiện một yêu cầu thông thường cho kết quả về tính toàn vẹn từ Google Play, bạn phải chuẩn bị (hoặc "khởi động") trình cung cấp mã thông báo về tính toàn vẹn. Điều này cho phép Google Play lưu một phần thông tin chứng thực trên thiết bị vào bộ nhớ đệm một cách thông minh để giảm độ trễ trên đường dẫn quan trọng khi bạn yêu cầu kết quả về tính toàn vẹn. Chuẩn bị lại trình cung cấp mã thông báo là một cách để lặp lại các bước kiểm tra tính toàn vẹn tốn ít tài nguyên hơn nhằm đưa ra kết quả tiếp theo mới hơn về tính toàn vẹn mà bạn yêu cầu.

Bạn có thể chuẩn bị trình cung cấp mã thông báo về tính toàn vẹn:

  • Khi ứng dụng khởi chạy (tức là khi khởi động nguội). Quá trình chuẩn bị trình cung cấp mã thông báo là quá trình không đồng bộ, do đó, sẽ không ảnh hưởng đến thời gian khởi động. Lựa chọn này sẽ hiệu quả nếu bạn dự định yêu cầu kết quả về tính toàn vẹn ngay sau khi ứng dụng khởi chạy, chẳng hạn như khi người dùng đăng nhập hoặc người chơi tham gia trò chơi.
  • Khi ứng dụng được mở (tức là khi khởi động ấm). Tuy nhiên, lưu ý rằng mỗi phiên bản ứng dụng chỉ có thể chuẩn bị mã thông báo về tính toàn vẹn tối đa 5 lần/phút.
  • Bất cứ lúc nào trong nền khi bạn muốn chuẩn bị mã thông báo trước khi thực hiện yêu cầu kết quả về tính toàn vẹn.

Để chuẩn bị trình cung cấp mã thông báo về tính toàn vẹn, hãy làm như sau:

  1. Tạo một StandardIntegrityManager như trong các ví dụ sau.
  2. Tạo PrepareIntegrityTokenRequest, cung cấp số dự án trên đám mây của Google thông qua phương thức setCloudProjectNumber().
  3. Sử dụng trình quản lý để gọi prepareIntegrityToken(), cung cấp PrepareIntegrityTokenRequest.

Java

import com.google.android.gms.tasks.Task;

// Create an instance of a manager.
StandardIntegrityManager standardIntegrityManager =
    IntegrityManagerFactory.createStandard(applicationContext);

StandardIntegrityTokenProvider integrityTokenProvider;
long cloudProjectNumber = ...;

// Prepare integrity token. Can be called once in a while to keep internal
// state fresh.
standardIntegrityManager.prepareIntegrityToken(
    PrepareIntegrityTokenRequest.builder()
        .setCloudProjectNumber(cloudProjectNumber)
        .build())
    .addOnSuccessListener(tokenProvider -> {
        integrityTokenProvider = tokenProvider;
    })
    .addOnFailureListener(exception -> handleError(exception));

Unity

IEnumerator PrepareIntegrityTokenCoroutine() {
    long cloudProjectNumber = ...;

    // Create an instance of a standard integrity manager.
    var standardIntegrityManager = new StandardIntegrityManager();

    // Request the token provider.
    var integrityTokenProviderOperation =
      standardIntegrityManager.PrepareIntegrityToken(
        new PrepareIntegrityTokenRequest(cloudProjectNumber));

    // Wait for PlayAsyncOperation to complete.
    yield return integrityTokenProviderOperation;

    // Check the resulting error code.
    if (integrityTokenProviderOperation.Error != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenProviderOperation.Error);
        yield break;
    }

    // Get the response.
    var integrityTokenProvider = integrityTokenProviderOperation.GetResult();
}

Mã gốc

/// Initialize StandardIntegrityManager
StandardIntegrityManager_init(/* app's java vm */, /* an android context */);
/// Create a PrepareIntegrityTokenRequest opaque object.
int64_t cloudProjectNumber = ...;
PrepareIntegrityTokenRequest* tokenProviderRequest;
PrepareIntegrityTokenRequest_create(&tokenProviderRequest);
PrepareIntegrityTokenRequest_setCloudProjectNumber(tokenProviderRequest, cloudProjectNumber);

/// Prepare a StandardIntegrityTokenProvider opaque type pointer and call
/// StandardIntegrityManager_prepareIntegrityToken().
StandardIntegrityTokenProvider* tokenProvider;
StandardIntegrityErrorCode error_code =
        StandardIntegrityManager_prepareIntegrityToken(tokenProviderRequest, &tokenProvider);

/// ...
/// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
if (error_code != STANDARD_INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.

IntegrityResponseStatus token_provider_status;

/// Check for error codes.
StandardIntegrityErrorCode error_code =
        StandardIntegrityTokenProvider_getStatus(tokenProvider, &token_provider_status);
if (error_code == STANDARD_INTEGRITY_NO_ERROR
    && token_provider_status == INTEGRITY_RESPONSE_COMPLETED)
{
    /// continue to request token from the token provider
}
/// ...
/// Remember to free up resources.
PrepareIntegrityTokenRequest_destroy(tokenProviderRequest);

Bảo vệ các yêu cầu khỏi hành vi can thiệp (nên dùng)

Khi kiểm tra hành động của người dùng trong ứng dụng bằng API Tính toàn vẹn của Play, bạn có thể tận dụng trường requestHash để giảm thiểu các cuộc tấn công can thiệp. Ví dụ: một trò chơi có thể cần báo cáo điểm số của người chơi tới máy chủ phụ trợ của trò chơi. Đồng thời, máy chủ của bạn muốn đảm bảo điểm số này không bị máy chủ proxy can thiệp. API Tính toàn vẹn của Play sẽ trả về giá trị bạn đặt trong trường requestHash, bên trong phản hồi về tính toàn vẹn đã ký. Nếu không có requestHash, mã thông báo về tính toàn vẹn sẽ chỉ được liên kết với thiết bị chứ không được liên kết với yêu cầu cụ thể, từ đó dẫn đến nguy cơ bị tấn công. Hướng dẫn sau đây mô tả cách sử dụng hiệu quả trường requestHash:

Khi bạn yêu cầu kết quả về tính toàn vẹn:

  • Tính toán chuỗi đại diện của tất cả các tham số yêu cầu có liên quan (ví dụ: SHA256 của quá trình chuyển đổi tuần tự yêu cầu có tính ổn định) dựa trên hành động của người dùng hoặc yêu cầu tới máy chủ đang diễn ra. Giá trị đã đặt trong trường requestHash có độ dài tối đa là 500 byte. Đưa mọi dữ liệu yêu cầu của ứng dụng có vai trò quan trọng hoặc liên quan đến hành động mà bạn đang kiểm tra hoặc bảo vệ vào requestHash. Trường requestHash được đưa nguyên văn vào mã thông báo về tính toàn vẹn, vì vậy, các giá trị dài có thể làm tăng kích thước yêu cầu.
  • Cung cấp chuỗi đại diện dưới dạng trường requestHash cho API Tính toàn vẹn của Play và nhận mã thông báo về tính toàn vẹn.

Khi bạn nhận được kết quả về tính toàn vẹn:

  • Giải mã mã thông báo về tính toàn vẹn và trích xuất trường requestHash.
  • Tính toán chuỗi đại diện của yêu cầu theo cách tương tự như trong ứng dụng (ví dụ: SHA256 của quá trình chuyển đổi tuần tự yêu cầu mang tính ổn định).
  • So sánh chuỗi đại diện phía ứng dụng và phía máy chủ. Nếu chúng không khớp nhau thì yêu cầu không đáng tin cậy.

Yêu cầu kết quả về tính toàn vẹn (theo yêu cầu)

Sau khi chuẩn bị trình cung cấp mã thông báo về tính toàn vẹn, bạn có thể bắt đầu yêu cầu Google Play cung cấp kết quả về tính toàn vẹn. Để làm được điều này, vui lòng hoàn thành các bước sau:

  1. Nhận một StandardIntegrityTokenProvider như minh hoạ ở trên.
  2. Tạo StandardIntegrityTokenRequest, cung cấp hàm băm yêu cầu về hành động của người dùng mà bạn muốn bảo vệ thông qua phương thức setRequestHash.
  3. Sử dụng trình cung cấp mã thông báo về tính toàn vẹn để gọi request(), cung cấp StandardIntegrityTokenRequest.

Java

import com.google.android.gms.tasks.Task;

StandardIntegrityTokenProvider integrityTokenProvider;

// See above how to prepare integrityTokenProvider.

// Request integrity token by providing a user action request hash. Can be called
// several times for different user actions.
String requestHash = "2cp24z...";
Task<StandardIntegrityToken> integrityTokenResponse =
    integrityTokenProvider.request(
        StandardIntegrityTokenRequest.builder()
            .setRequestHash(requestHash)
            .build());
integrityTokenResponse
    .addOnSuccessListener(response -> sendToServer(response.token()))
    .addOnFailureListener(exception -> handleError(exception));

Unity

IEnumerator RequestIntegrityTokenCoroutine() {
    StandardIntegrityTokenProvider integrityTokenProvider;

    // See above how to prepare integrityTokenProvider.

    // Request integrity token by providing a user action request hash. Can be called
    // several times for different user actions.
    String requestHash = "2cp24z...";
    var integrityTokenOperation = integrityTokenProvider.Request(
      new StandardIntegrityTokenRequest(requestHash)
    );

    // Wait for PlayAsyncOperation to complete.
    yield return integrityTokenOperation;

    // Check the resulting error code.
    if (integrityTokenOperation.Error != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenOperation.Error);
        yield break;
    }

    // Get the response.
    var integrityToken = integrityTokenOperation.GetResult();
}

Mã gốc

/// Create a StandardIntegrityTokenRequest opaque object.
const char* requestHash = ...;
StandardIntegrityTokenRequest* tokenRequest;
StandardIntegrityTokenRequest_create(&tokenRequest);
StandardIntegrityTokenRequest_setRequestHash(tokenRequest, requestHash);

/// Prepare a StandardIntegrityToken opaque type pointer and call
/// StandardIntegrityTokenProvider_request(). Can be called several times for
/// different user actions. See above how to prepare token provider.
StandardIntegrityToken* token;
StandardIntegrityErrorCode error_code =
        StandardIntegrityTokenProvider_request(tokenProvider, tokenRequest, &token);

/// ...
/// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
if (error_code != STANDARD_INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.

IntegrityResponseStatus token_status;

/// Check for error codes.
StandardIntegrityErrorCode error_code =
        StandardIntegrityToken_getStatus(token, &token_status);
if (error_code == STANDARD_INTEGRITY_NO_ERROR
    && token_status == INTEGRITY_RESPONSE_COMPLETED)
{
    const char* integrityToken = StandardIntegrityToken_getToken(token);
}
/// ...
/// Remember to free up resources.
StandardIntegrityTokenRequest_destroy(tokenRequest);
StandardIntegrityToken_destroy(token);
StandardIntegrityTokenProvider_destroy(tokenProvider);
StandardIntegrityManager_destroy();

Giải mã và xác minh kết quả về tính toàn vẹn

Sau khi bạn yêu cầu kết quả về tính toàn vẹn, API Tính toàn vẹn của Play sẽ cung cấp mã thông báo phản hồi đã mã hoá. Để có được kết quả về tính toàn vẹn của thiết bị, bạn phải giải mã mã thông báo tính toàn vẹn trên các máy chủ của Google. Cách làm như sau:

  1. Tạo một tài khoản dịch vụ trong dự án Google Cloud được liên kết với ứng dụng của bạn. Trong quá trình tạo tài khoản này, bạn cần cấp cho tài khoản dịch vụ của mình vai trò Service Account User (Người dùng tài khoản dịch vụ) và Service Usage Consumer (Người tiêu dùng sử dụng dịch vụ).
  2. Trên máy chủ của ứng dụng, hãy tìm nạp mã truy cập từ thông tin xác thực tài khoản dịch vụ của bạn bằng phạm vi playintegrity và thực hiện yêu cầu sau:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Đọc phản hồi JSON.

Tải trọng phát sinh là một mã thông báo dạng văn bản thuần tuý chứa các kết quả về tính toàn vẹn.

Tự động bảo vệ khỏi các cuộc tấn công phát lại

Để giảm thiểu các cuộc tấn công phát lại, Google Play tự động đảm bảo rằng từng mã thông báo về tính toàn vẹn sẽ không thể được dùng lại nhiều lần. Việc cố gắng giải mã liên tục cùng một mã thông báo sẽ dẫn đến kết quả trống.