สร้างคำขอ 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 เล่นเพื่อแคชข้อมูลเอกสารรับรองบางส่วนในอุปกรณ์อย่างชาญฉลาดเพื่อ ลดเวลาในการตอบสนองในเส้นทางสำคัญเมื่อคุณส่งคำขอ การตัดสินความสมบูรณ์ การเตรียมผู้ให้บริการโทเค็นอีกครั้งเป็นวิธีที่จะทำซ้ำน้อยลง การตรวจสอบความสมบูรณ์ของทรัพยากรจำนวนมาก ซึ่งจะ ตัดสินความสมบูรณ์ครั้งต่อไป ที่คุณขอเพิ่มเติม

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

  • เมื่อแอปของคุณเปิดขึ้น (เช่น เมื่อ Cold Start) กำลังเตรียมผู้ให้บริการโทเค็น ทำงานไม่พร้อมกัน จึงไม่ส่งผลกระทบต่อเวลาเริ่มต้น ตัวเลือกนี้จะ จะทำงานได้ดีหากคุณวางแผนที่จะส่งคำขอการตัดสินความสมบูรณ์หลังจาก แอปเปิดขึ้น ตัวอย่างเช่น เมื่อผู้ใช้ลงชื่อเข้าใช้หรือผู้เล่นเข้าร่วมเกม
  • เมื่อเปิดแอป (เช่น เมื่อ Warm Start) แต่โปรดทราบว่าแต่ละแอป อินสแตนซ์สามารถเตรียมโทเค็นความสมบูรณ์ได้สูงสุด 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));

เอกภาพ

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

เอกภาพ

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 จะตรวจสอบโดยอัตโนมัติว่า โทเค็นความสมบูรณ์ไม่สามารถนำมาใช้ซ้ำหลายครั้ง กำลังพยายามถอดรหัส โทเค็นเดียวกันจะทำให้การตัดสินว่างเปล่า