สร้างคำขอ 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

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