หน้านี้อธิบายการส่งคําขอ API มาตรฐานสําหรับผลการตรวจสอบความสมบูรณ์ ซึ่งใช้ได้ใน Android 5.0 (API ระดับ 21) ขึ้นไป คุณส่งคำขอ API มาตรฐานเพื่อขอผลการตรวจสอบความสมบูรณ์ได้ทุกครั้งที่แอปทำการเรียกเซิร์ฟเวอร์เพื่อตรวจสอบว่าการโต้ตอบนั้นเกิดขึ้นจริงหรือไม่
ภาพรวม
คำขอมาตรฐานประกอบด้วย 2 ส่วน ดังนี้
- เตรียมผู้ให้บริการโทเค็นความสมบูรณ์ (แบบครั้งเดียว): คุณต้องเรียกใช้ Integrity API เพื่อเตรียมผู้ให้บริการโทเค็นความสมบูรณ์ให้พร้อมก่อนที่คุณจะต้องรับผลการตรวจสอบความสมบูรณ์ เช่น คุณอาจดำเนินการนี้เมื่อแอปเปิดขึ้นหรือทำงานอยู่เบื้องหลังก่อนที่จะต้องมีการตัดสินความสมบูรณ์
- ขอโทเค็นความสมบูรณ์ (ตามคําขอ): เมื่อใดก็ตามที่แอปส่งคําขอไปยังเซิร์ฟเวอร์ซึ่งคุณต้องการตรวจสอบว่าเป็นคําขอจริง คุณจะขอโทเค็นความสมบูรณ์และส่งไปยังเซิร์ฟเวอร์แบ็กเอนด์ของแอปเพื่อถอดรหัสและยืนยัน จากนั้นเซิร์ฟเวอร์แบ็กเอนด์จะตัดสินใจว่าจะดำเนินการอย่างไร
เตรียมผู้ให้บริการโทเค็นความสมบูรณ์ (แบบครั้งเดียว)
- แอปเรียกผู้ให้บริการโทเค็นความสมบูรณ์ด้วยหมายเลขโปรเจ็กต์ Google Cloud
- แอปของคุณจะเก็บผู้ให้บริการโทเค็นความสมบูรณ์ไว้ในหน่วยความจำเพื่อเรียกใช้การตรวจสอบการรับรองเพิ่มเติม
ขอโทเค็นความสมบูรณ์ (แบบออนดีมานด์)
- สําหรับการดําเนินการของผู้ใช้ที่ต้องได้รับการปกป้อง แอปจะคํานวณแฮช (โดยใช้อัลกอริทึมการแฮชที่เหมาะสม เช่น SHA256) ของคําขอที่จะส่ง
- แอปของคุณขอโทเค็นความสมบูรณ์โดยส่งแฮชคำขอ
- แอปของคุณจะได้รับโทเค็นความสมบูรณ์ที่เซ็นชื่อและเข้ารหัสจาก Play Integrity API
- แอปของคุณส่งโทเค็นความสมบูรณ์ไปยังแบ็กเอนด์ของแอป
- แบ็กเอนด์ของแอปจะส่งโทเค็นไปยังเซิร์ฟเวอร์ Google Play เซิร์ฟเวอร์ Google Play จะถอดรหัสและยืนยันผลการตัดสิน จากนั้นส่งผลลัพธ์ไปยังแบ็กเอนด์ของแอป
- แบ็กเอนด์ของแอปจะเป็นผู้ตัดสินใจว่าจะดำเนินการต่ออย่างไร โดยอิงตามสัญญาณที่อยู่ในเพย์โหลดโทเค็น
- แบ็กเอนด์ของแอปจะส่งผลลัพธ์การตัดสินไปยังแอป
เตรียมผู้ให้บริการโทเค็นความสมบูรณ์ (แบบครั้งเดียว)
คุณต้องเตรียม (หรือ "อุ่นเครื่อง") ผู้ให้บริการโทเค็นความสมบูรณ์ก่อนส่งคำขอมาตรฐานเพื่อขอคำตัดสินความสมบูรณ์จาก Google Play ซึ่งช่วยให้ Google Play แคชข้อมูลการรับรองบางส่วนในอุปกรณ์ได้อย่างชาญฉลาดเพื่อลดความล่าช้าในเส้นทางที่สำคัญเมื่อคุณส่งคำขอคำตัดสินความสมบูรณ์ การเตรียมผู้ให้บริการโทเค็นอีกครั้งเป็นวิธีที่จะทำการตรวจสอบความสมบูรณ์แบบไม่ใช้ทรัพยากรมากซ้ำ ซึ่งจะทำให้ผลการตรวจสอบความสมบูรณ์ครั้งถัดไปที่คุณขอเป็นข้อมูลล่าสุดมากขึ้น
คุณอาจเตรียมผู้ให้บริการโทเค็นความสมบูรณ์ดังนี้
- เมื่อแอปเปิดขึ้น (เช่น เมื่อเปิดแอปครั้งแรก) การเตรียมผู้ให้บริการโทเค็นจะทำงานแบบไม่พร้อมกัน จึงจะไม่ส่งผลต่อเวลาเริ่มต้น ตัวเลือกนี้จะได้ผลดีหากคุณวางแผนที่จะส่งคำขอการตัดสินความสมบูรณ์ไม่นานหลังจากที่เปิดตัวแอป เช่น เมื่อผู้ใช้ลงชื่อเข้าใช้หรือผู้เล่นเข้าร่วมเกม
- เมื่อแอปเปิดอยู่ (เช่น ในการเริ่มต้นแบบอุ่น) อย่างไรก็ตาม โปรดทราบว่าอินสแตนซ์แอปแต่ละรายการจะเตรียมโทเค็นความสมบูรณ์ได้สูงสุด 5 ครั้งต่อนาที
- ได้ทุกเมื่อในเบื้องหลังเมื่อคุณต้องการเตรียมโทเค็นล่วงหน้าสำหรับคำขอผลการตรวจสอบความสมบูรณ์
วิธีเตรียมผู้ให้บริการโทเค็นความสมบูรณ์มีดังนี้
- สร้าง
StandardIntegrityManager
ตามที่แสดงในตัวอย่างต่อไปนี้ - สร้าง
PrepareIntegrityTokenRequest
โดยระบุหมายเลขโปรเจ็กต์ Google Cloud ผ่านเมธอดsetCloudProjectNumber()
- ใช้ตัวจัดการเพื่อโทรหา
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(); }
เนทีฟ
/// 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 ได้ โดยทำตามขั้นตอนต่อไปนี้
- รับ
StandardIntegrityTokenProvider
ดังที่แสดงด้านบน - สร้าง
StandardIntegrityTokenRequest
โดยระบุแฮชคำขอของการดำเนินการของผู้ใช้ที่ต้องการปกป้องผ่านเมธอดsetRequestHash
- ใช้ผู้ให้บริการโทเค็นความสมบูรณ์เพื่อเรียก
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(); }
เนทีฟ
/// 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();
ถอดรหัสและยืนยันการตัดสินความสมบูรณ์
หลังจากขอผลการตัดสินความสมบูรณ์แล้ว Play Integrity API จะแสดงโทเค็นการตอบกลับที่เข้ารหัส หากต้องการดูผลการตัดสินความสมบูรณ์ของอุปกรณ์ คุณต้องถอดรหัสโทเค็นความสมบูรณ์ในเซิร์ฟเวอร์ของ Google โดยทำตามขั้นตอนต่อไปนี้
- สร้างบัญชีบริการภายในโปรเจ็กต์ Google Cloud ที่ลิงก์กับแอป
ในเซิร์ฟเวอร์ของแอป ให้ดึงข้อมูลโทเค็นการเข้าถึงจากข้อมูลเข้าสู่ระบบบัญชีบริการโดยใช้ขอบเขต playintegrity แล้วส่งคำขอต่อไปนี้
playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \ '{ "integrity_token": "INTEGRITY_TOKEN" }'
อ่านการตอบกลับ JSON
เพย์โหลดที่ได้จะเป็นโทเค็นข้อความธรรมดาที่มีผลการตรวจสอบความสมบูรณ์
การป้องกันการเล่นซ้ำอัตโนมัติ
Google Play จะตรวจสอบโดยอัตโนมัติว่าโทเค็นความสมบูรณ์แต่ละรายการใช้ซ้ำหลายครั้งไม่ได้ เพื่อลดการโจมตีด้วยการเล่นซ้ำ การพยายามถอดรหัสโทเค็นเดียวกันซ้ำๆ จะทำให้ระบบตัดสินว่าไม่พบเนื้อหาที่อาจไม่เหมาะสม สําหรับโทเค็นที่มีการป้องกันการเล่นซ้ำ ระบบจะแสดงผลการตรวจสอบที่ถอดรหัสแล้วดังนี้
- ผลการตรวจสอบการจดจําอุปกรณ์จะว่างเปล่า
- ระบบจะตั้งค่าผลการตัดสินการจดจําแอปและผลการตัดสินการอนุญาตให้ใช้แอปเป็น
UNEVALUATED
- ระบบจะตั้งค่าผลการตรวจสอบที่ไม่บังคับซึ่งเปิดใช้โดยใช้ Play Console เป็น
UNEVALUATED
(หรือเป็นผลการตรวจสอบว่างหากเป็นผลการตรวจสอบแบบหลายค่า)