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

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

ภาพรวม

แผนภาพลำดับที่แสดงการออกแบบระดับสูงของ 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();
}

เนทีฟ

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

เนทีฟ

/// 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 โดยทำตามขั้นตอนต่อไปนี้

  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 (หรือเป็นผลการตรวจสอบว่างหากเป็นผลการตรวจสอบแบบหลายค่า)