สร้างคำขอ API มาตรฐาน

หน้านี้อธิบายการส่งคำขอ API มาตรฐานสำหรับคำตัดสินด้านความสมบูรณ์ ซึ่งรองรับใน Android 5.0 (API ระดับ 21) ขึ้นไป คุณสามารถส่งคำขอ API มาตรฐานเพื่อขอผลการตรวจสอบความสมบูรณ์ได้ทุกครั้งที่แอปเรียกเซิร์ฟเวอร์ เพื่อตรวจสอบว่าการโต้ตอบนั้นเป็นของแท้หรือไม่

ภาพรวม

รูปที่ 1 แผนภาพลำดับที่แสดงการออกแบบระดับสูงของ Play Integrity API

คำขอมาตรฐานประกอบด้วย 2 ส่วน ดังนี้

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

เตรียมผู้ให้บริการโทเค็นความสมบูรณ์ (ครั้งเดียว):

  1. แอปของคุณจะเรียกผู้ให้บริการโทเค็นความสมบูรณ์ด้วยหมายเลขโปรเจ็กต์ Google Cloud
  2. แอปของคุณจะเก็บผู้ให้บริการโทเค็นความสมบูรณ์ไว้ในหน่วยความจำสำหรับการเรียกการตรวจสอบการรับรองเพิ่มเติม

ขอโทเค็นความสมบูรณ์ (ตามต้องการ)

  1. สําหรับการกระทําของผู้ใช้ที่ต้องได้รับการปกป้อง แอปของคุณจะคํานวณแฮช (โดยใช้อัลกอริทึมแฮชที่เหมาะสม เช่น SHA256) ของคําขอที่จะส่ง
  2. แอปของคุณขอโทเค็นความสมบูรณ์โดยส่งแฮชคำขอ
  3. แอปของคุณจะได้รับโทเค็นความสมบูรณ์ที่ลงนามและเข้ารหัสจาก Play Integrity API
  4. แอปส่งโทเค็นความสมบูรณ์ไปยังแบ็กเอนด์ของแอป
  5. แบ็กเอนด์ของแอปจะส่งโทเค็นไปยังเซิร์ฟเวอร์ของ Google Play เซิร์ฟเวอร์ Google Play จะถอดรหัสและยืนยันผลการตัดสิน แล้วส่งผลลัพธ์ไปยังแบ็กเอนด์ของแอป
  6. แบ็กเอนด์ของแอปจะเป็นตัวกำหนดวิธีดำเนินการต่อ โดยอิงตามสัญญาณที่มีอยู่ใน เพย์โหลดของโทเค็น
  7. แบ็กเอนด์ของแอปจะส่งผลลัพธ์ของคำตัดสินไปยังแอป

เตรียมผู้ให้บริการโทเค็นความสมบูรณ์ (ครั้งเดียว)

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

คุณอาจเตรียมผู้ให้บริการโทเค็นความสมบูรณ์ดังนี้

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

หากต้องการเตรียมผู้ให้บริการโทเค็นความสมบูรณ์ ให้ทำดังนี้

  1. สร้าง StandardIntegrityManager ตามที่แสดงในตัวอย่างต่อไปนี้
  2. สร้าง PrepareIntegrityTokenRequest โดยระบุหมายเลขโปรเจ็กต์ Google Cloud ผ่านเมธอด setCloudProjectNumber()
  3. ใช้ตัวจัดการเพื่อเรียกใช้ prepareIntegrityToken() โดยระบุ 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();
}

Unreal Engine

// .h
void MyClass::OnPrepareIntegrityTokenCompleted(
  EStandardIntegrityErrorCode ErrorCode,
  UStandardIntegrityTokenProvider* Provider)
{
  // Check the resulting error code.
  if (ErrorCode == EStandardIntegrityErrorCode::StandardIntegrity_NO_ERROR)
  {
    // ...
  }
}

// .cpp
void MyClass::PrepareIntegrityToken()
{
  int64 CloudProjectNumber = ...

  // Create the Integrity Token Request.
  FPrepareIntegrityTokenRequest Request = { CloudProjectNumber };

  // Create a delegate to bind the callback function.
  FPrepareIntegrityOperationCompletedDelegate Delegate;

  // Bind the completion handler (OnPrepareIntegrityTokenCompleted) to the delegate.
  Delegate.BindDynamic(this, &MyClass::OnPrepareIntegrityTokenCompleted);

  // Initiate the prepare integrity token operation, passing the delegate to handle the result.
  GetGameInstance()
    ->GetSubsystem<UStandardIntegrityManager>()
    ->PrepareIntegrityToken(Request, Delegate);
}

เนทีฟ

/// 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);

ป้องกันการดัดแปลงคำขอ (แนะนำ)

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

เมื่อขอการตัดสินความสมบูรณ์

  • คำนวณข้อมูลสรุปของพารามิเตอร์คำขอที่เกี่ยวข้องทั้งหมด (เช่น SHA256 ของการซีเรียลไลซ์คำขอที่เสถียร) จากการกระทำของผู้ใช้หรือคำขอของเซิร์ฟเวอร์ที่เกิดขึ้น ค่าที่ตั้งไว้ในช่อง requestHash มีความยาวสูงสุด 500 ไบต์ รวมข้อมูลคำขอของแอปใน requestHash ที่มีความสําคัญหรือเกี่ยวข้องกับการกระทําที่คุณกําลังตรวจสอบหรือปกป้อง ระบบจะรวมฟิลด์ requestHashไว้ในโทเค็นความสมบูรณ์ตามตัวอักษร ดังนั้นค่า ที่ยาวอาจเพิ่มขนาดคำขอ
  • ระบุข้อมูลสรุปเป็นฟิลด์ requestHash ให้กับ Play Integrity API และ รับโทเค็นความสมบูรณ์

เมื่อได้รับการตัดสินความสมบูรณ์ ให้ทำดังนี้

  • ถอดรหัสโทเค็นความสมบูรณ์และแยกฟิลด์ requestHash
  • คำนวณข้อมูลสรุปของคำขอในลักษณะเดียวกับในแอป (เช่น SHA256 ของการซีเรียลไลซ์คำขอที่เสถียร)
  • เปรียบเทียบข้อมูลสรุปฝั่งแอปและฝั่งเซิร์ฟเวอร์ หากไม่ตรงกัน แสดงว่าคำขอไม่น่าเชื่อถือ

ขอการตัดสินความสมบูรณ์ (ตามต้องการ)

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

  1. รับ StandardIntegrityTokenProvider
  2. สร้าง StandardIntegrityTokenRequest โดยระบุแฮชคำขอของการดำเนินการของผู้ใช้ที่คุณต้องการปกป้องผ่านเมธอด setRequestHash
  3. ใช้ผู้ให้บริการโทเค็นความสมบูรณ์เพื่อเรียกใช้ request() โดยระบุ 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();
}

Unreal Engine

// .h
void MyClass::OnRequestIntegrityTokenCompleted(
  EStandardIntegrityErrorCode ErrorCode,
  UStandardIntegrityToken* Response)
{
  // Check the resulting error code.
  if (ErrorCode == EStandardIntegrityErrorCode::StandardIntegrity_NO_ERROR)
  {
    // Get the token.
    FString Token = Response->Token;
  }
}

// .cpp
void MyClass::RequestIntegrityToken()
{
  UStandardIntegrityTokenProvider* Provider = ...

  // Prepare the UStandardIntegrityTokenProvider.

  // Request integrity token by providing a user action request hash. Can be called
  // several times for different user actions.
  FString RequestHash = ...;
  FStandardIntegrityTokenRequest Request = { RequestHash };

  // Create a delegate to bind the callback function.
  FStandardIntegrityOperationCompletedDelegate Delegate;

  // Bind the completion handler (OnRequestIntegrityTokenCompleted) to the delegate.
  Delegate.BindDynamic(this, &MyClass::OnRequestIntegrityTokenCompleted);

  // Initiate the standard integrity token request, passing the delegate to handle the result.
  Provider->Request(Request, Delegate);
}

เนทีฟ

/// 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();

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

ถอดรหัสและยืนยันการตัดสินความสมบูรณ์

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

  1. สร้างบัญชีบริการ ภายในโปรเจ็กต์ Google Cloud ที่ลิงก์กับแอป
  2. ในเซิร์ฟเวอร์ของแอป ให้ดึงโทเค็นการเข้าถึงจากข้อมูลเข้าสู่ระบบบัญชีบริการ โดยใช้ขอบเขต playintegrity แล้วส่งคำขอต่อไปนี้

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. อ่านการตอบกลับ JSON

เพย์โหลดที่ได้คือโทเค็นข้อความธรรมดาที่มีคำตัดสินด้านความสมบูรณ์

การป้องกันการเล่นซ้ำโดยอัตโนมัติ

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

  • ผลการตัดสินการจดจำอุปกรณ์จะว่างเปล่า
  • ระบบจะตั้งค่าผลการตัดสินการจดจำแอปและผลการตัดสินการให้ใบอนุญาตแอปเป็น UNEVALUATED
  • คำตัดสินที่ไม่บังคับใดก็ตามที่เปิดใช้โดยใช้ Play Console จะ ตั้งค่าเป็น UNEVALUATED (หรือเป็นคำตัดสินที่ว่างเปล่าหากเป็นคำตัดสินแบบหลายค่า)

แก้ไขปัญหาเกี่ยวกับผลการตัดสินด้วยข้อความแจ้งของ Google Play (ไม่บังคับ)

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

Play Integrity API มีตัวเลือกในการแสดงกล่องโต้ตอบของ Google Play ที่ แจ้งให้ผู้ใช้ดำเนินการ เช่น ดาวน์โหลดแอปเวอร์ชันทางการ จาก Google Play

ดูวิธีทริกเกอร์กล่องโต้ตอบเหล่านี้จากแอปตามการตอบกลับของเซิร์ฟเวอร์ได้ที่กล่องโต้ตอบการแก้ไข