หน้านี้อธิบายเกี่ยวกับการสร้างคำขอ API มาตรฐานสำหรับการตัดสินความสมบูรณ์ ซึ่ง ใช้ได้ใน Android 5.0 (API ระดับ 21) ขึ้นไป คุณสามารถสร้างมาตรฐาน คําขอ API สําหรับการตัดสินความสมบูรณ์ทุกครั้งที่แอปเรียกใช้เซิร์ฟเวอร์ เพื่อตรวจสอบว่าการโต้ตอบนั้นเป็นเรื่องจริงหรือไม่
ภาพรวม
คำขอมาตรฐานประกอบด้วย 2 ส่วน ดังนี้
- เตรียมผู้ให้บริการโทเค็นความสมบูรณ์ (แบบครั้งเดียว): คุณต้องเรียกผู้ให้บริการ Integrity API เพื่อเตรียมผู้ให้บริการโทเค็นความสมบูรณ์ให้พร้อมก่อนที่คุณจะต้อง เพื่อรับการตัดสินความสมบูรณ์ ตัวอย่างเช่น คุณสามารถดำเนินการนี้ได้ในกรณีที่แอปของคุณ เปิดขึ้นหรือเล่นอยู่เบื้องหลังก่อนที่จะต้องตัดสินความสมบูรณ์
- ขอโทเค็นความสมบูรณ์ (ตามคำขอ): เมื่อใดก็ตามที่แอปสร้างเซิร์ฟเวอร์ ที่คุณต้องการตรวจสอบว่าเป็นของแท้ คุณขอโทเค็นความสมบูรณ์ แล้วส่งไปยังเซิร์ฟเวอร์แบ็กเอนด์ของแอปเพื่อถอดรหัสและยืนยัน จากนั้นเซิร์ฟเวอร์แบ็กเอนด์จะเลือกได้ว่าจะดำเนินการอย่างไร
เตรียมผู้ให้บริการโทเค็นความสมบูรณ์ (แบบครั้งเดียว) ดังนี้
- แอปของคุณเรียกใช้ผู้ให้บริการโทเค็นความสมบูรณ์ด้วยโปรเจ็กต์ Google Cloud ของคุณ หมายเลข
- แอปของคุณจะเก็บผู้ให้บริการโทเค็นความสมบูรณ์ไว้ในหน่วยความจำเพื่อการดำเนินการเพิ่มเติม การโทรตรวจสอบเอกสารรับรอง
ขอโทเค็นความสมบูรณ์ (ตามคำขอ)
- สำหรับการดำเนินการของผู้ใช้ที่จำเป็นต้องมีการปกป้อง แอปของคุณจะคำนวณแฮช (โดยใช้อัลกอริทึมแฮชที่เหมาะสม เช่น SHA256) ของคำขอที่จะทำ
- แอปของคุณขอโทเค็นความสมบูรณ์โดยการส่งแฮชของคําขอ
- แอปของคุณได้รับโทเค็นความสมบูรณ์ที่ลงชื่อและเข้ารหัสจาก Play Integrity API
- แอปของคุณส่งโทเค็นความสมบูรณ์ไปยังแบ็กเอนด์ของแอป
- แบ็กเอนด์ของแอปจะส่งโทเค็นไปยังเซิร์ฟเวอร์ Google Play Google Play เซิร์ฟเวอร์ถอดรหัสและยืนยันผลการตัดสิน จากนั้นส่งผลลัพธ์ไปยังแอปของคุณ แบ็กเอนด์
- แบ็กเอนด์ของแอปจะช่วยกำหนดวิธีดำเนินการต่อโดยพิจารณาจากสัญญาณที่มีอยู่ใน เพย์โหลดโทเค็น
- แบ็กเอนด์ของแอปจะส่งผลการตัดสินใจไปยังแอป
เตรียมผู้ให้บริการโทเค็นความสมบูรณ์ (แบบครั้งเดียว)
ก่อนที่คุณจะส่งคำขอแบบมาตรฐานสำหรับการตัดสินความสมบูรณ์จาก Google Play คุณต้องเตรียม (หรือ "อุ่นเครื่อง") ผู้ให้บริการโทเค็นความสมบูรณ์ วิธีนี้จะช่วยให้ Google เล่นเพื่อแคชข้อมูลเอกสารรับรองบางส่วนในอุปกรณ์อย่างชาญฉลาดเพื่อ ลดเวลาในการตอบสนองในเส้นทางสำคัญเมื่อคุณส่งคำขอ การตัดสินความสมบูรณ์ การเตรียมผู้ให้บริการโทเค็นอีกครั้งเป็นวิธีที่จะทำซ้ำน้อยลง การตรวจสอบความสมบูรณ์ของทรัพยากรจำนวนมาก ซึ่งจะ ตัดสินความสมบูรณ์ครั้งต่อไป ที่คุณขอเพิ่มเติม
คุณอาจเตรียมผู้ให้บริการโทเค็นความสมบูรณ์ได้ดังนี้
- เมื่อแอปของคุณเปิดขึ้น (เช่น เมื่อ Cold Start) กำลังเตรียมผู้ให้บริการโทเค็น ทำงานไม่พร้อมกัน จึงไม่ส่งผลกระทบต่อเวลาเริ่มต้น ตัวเลือกนี้จะ จะทำงานได้ดีหากคุณวางแผนที่จะส่งคำขอการตัดสินความสมบูรณ์หลังจาก แอปเปิดขึ้น ตัวอย่างเช่น เมื่อผู้ใช้ลงชื่อเข้าใช้หรือผู้เล่นเข้าร่วมเกม
- เมื่อเปิดแอป (เช่น เมื่อ Warm Start) แต่โปรดทราบว่าแต่ละแอป อินสแตนซ์สามารถเตรียมโทเค็นความสมบูรณ์ได้สูงสุด 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));
เอกภาพ
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));
เอกภาพ
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 จะตรวจสอบโดยอัตโนมัติว่า โทเค็นความสมบูรณ์ไม่สามารถนำมาใช้ซ้ำหลายครั้ง กำลังพยายามถอดรหัส โทเค็นเดียวกันจะทำให้การตัดสินว่างเปล่า