שליחת בקשה רגילה ל-API

בדף הזה מוסבר איך ליצור בקשות API רגילות לקביעות תקינות, שנתמכות ב-Android מגרסה 5.0 (רמת API‏ 21) ואילך. אתם יכולים לשלוח בקשת API רגילה לקבלת קביעת תקינות בכל פעם שהאפליקציה שולחת קריאה לשרת כדי לבדוק אם האינטראקציה היא אמיתית.

סקירה כללית

איור 1. דיאגרמת רצף שמציגה את העיצוב הכללי של Play Integrity API.

בקשה רגילה מורכבת משני חלקים:

  • הכנת ספק טוקן היושרה (חד-פעמי): צריך לבצע קריאה ל-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 באמצעות method 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 כדי לצמצם את הסיכון להתקפות של שיבוש. לדוגמה, יכול להיות שמשחק ירצה לדווח על הניקוד של השחקן לשרת העורפי של המשחק, והשרת שלכם ירצה לוודא ששרת proxy לא שינה את הניקוד הזה. ‫Play Integrity API מחזיר את הערך שהגדרתם בשדה requestHash, בתוך תגובת היושרה החתומה. בלי requestHash, טוקן השלמות יהיה קשור רק למכשיר, ולא לבקשה הספציפית, מה שפותח פתח למתקפה. בהוראות הבאות מוסבר איך להשתמש בשדה requestHash בצורה יעילה:

כשמבקשים קביעת תקינות:

  • מחשבים תקציר של כל פרמטרי הבקשה הרלוונטיים (למשל, SHA256 של סדרת בקשות יציבה) מפעולת המשתמש או מבקשת השרת שמתרחשת. האורך המקסימלי של הערך שמוגדר בשדה requestHash הוא 500 בייטים. כוללים ב-requestHash את כל נתוני הבקשות של האפליקציה שחשובים או רלוונטיים לפעולה שאתם בודקים או מגנים עליה. השדה requestHash נכלל באסימון השלמות כלשונו, ולכן ערכים ארוכים עשויים להגדיל את גודל הבקשה.
  • מעבירים את התקציר בתור השדה requestHash אל Play Integrity API ומקבלים את טוקן התקינות.

כשמקבלים קביעת תקינות:

  • מפענחים את אסימון השלמות ומחלצים את השדה requestHash.
  • מחשבים את התקציר של הבקשה באותו אופן שבו הוא מחושב באפליקציה (למשל, SHA256 של סדרת בקשות יציבה).
  • משווים בין התקצירים בצד האפליקציה ובצד השרת. אם הם לא תואמים, הבקשה לא מהימנה.

בקשת קביעת תקינות (על פי דרישה)

אחרי שמכינים את ספק אסימוני היושרה, אפשר להתחיל לבקש קביעות יושרה מ-Google Play. כדי לעשות זאת, פועלים לפי השלבים הבאים:

  1. קבלת StandardIntegrityTokenProvider
  2. יוצרים StandardIntegrityTokenRequest ומספקים את הגיבוב (hash) של הבקשה של פעולת המשתמש שרוצים להגן עליה באמצעות השיטה 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.

המטען הייעודי (payload) שמתקבל הוא טוקן של טקסט פשוט שמכיל פסקי דין שלמות.

הגנה אוטומטית מפני הפעלה חוזרת

כדי לצמצם את הסיכון למתקפות שידור חוזר, Google Play מונע באופן אוטומטי שימוש חוזר באסימוני תקינות פעמים רבות. ניסיון חוזר לפענח את אותו אסימון יגרום לניקוי של פסיקות הדין באופן הבא:

  • ההחלטה לגבי זיהוי המכשיר תהיה ריקה.
  • התוצאה של זיהוי האפליקציה והתוצאה של רישוי האפליקציה יוגדרו לערך UNEVALUATED.
  • כל אחת מההכרעות האופציונליות שמופעלות באמצעות Play Console תוגדר כ-UNEVALUATED (או כהכרעה ריקה אם מדובר בהכרעה מרובת ערכים).

תיקון בעיות שקשורות לתוצאות הבדיקה באמצעות הנחיה ב-Google Play (אופציונלי)

אחרי שהשרת מקבל את תוצאת הבדיקה של תקינות המכשיר, הוא יכול לקבוע איך להמשיך. אם פסק הדין מצביע על בעיה – למשל, האפליקציה לא מורשית, נעשה בה שימוש לרעה או שהמכשיר נפרץ – אתם יכולים לתת למשתמשים הזדמנות לתקן את הבעיה בעצמם.

‫Play Integrity API מספק אפשרות להציג תיבת דו-שיח של Google Play שמבקשת מהמשתמש לבצע פעולה, למשל להוריד את הגרסה הרשמית של האפליקציה מ-Google Play.

במאמר תיבות דו-שיח לתיקון בעיות מוסבר איך להפעיל את תיבות הדו-שיח האלה מהאפליקציה על סמך התגובה של השרת.