Как выполнить стандартный запрос к API

На этой странице описано, как отправлять стандартные запросы к API для получения оценок целостности. Такие запросы поддерживаются на устройствах с Android 5.0 (уровень API 21) или более поздней версии. Вы можете отправлять стандартный запрос к API для получения оценки целостности каждый раз, когда ваше приложение выполняет вызов сервера, чтобы проверить, является ли взаимодействие подлинным.

Обзор

Рисунок 1. Диаграмма последовательности, на которой показана общая схема работы Play Integrity API.

Стандартный запрос состоит из двух частей:

  • Подготовка поставщика токенов целостности (выполняется один раз). Задолго до того, как получить оценку целостности, нужно вызвать Play 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 кешировать на устройстве часть информации о подтверждении, чтобы уменьшить задержку на критическом пути при запросе оценки целостности. Повторная подготовка поставщика токенов позволяет повторить менее ресурсоемкие проверки целостности, что сделает следующую оценку целостности, которую вы запрашиваете, более актуальной.

Подготовить поставщика токенов целостности можно:

  • при запуске приложения (холодный запуск); Подготовка поставщика токенов выполняется асинхронно и не влияет на время запуска. Этот вариант подойдет, если вы планируете запрашивать оценку целостности вскоре после запуска приложения, например когда пользователь входит в аккаунт или присоединяется к игре.
  • при открытии приложения (теплый запуск); Однако обратите внимание, что каждый экземпляр приложения может подготавливать токен целостности не более пяти раз в минуту.
  • В любое время в фоновом режиме, чтобы подготовить токен до поступления запроса на оценку целостности.

Чтобы подготовить поставщик токенов целостности, сделайте следующее:

  1. Создайте StandardIntegrityManager, как показано в примерах ниже.
  2. Сформируйте запрос PrepareIntegrityTokenRequest и с помощью метода setCloudProjectNumber() укажите в нем номер проекта Google Cloud.
  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 = ...;

    // Initialize the V2 manager. Requires Play Integrity Unity Plugin v2.0.0
    // or higher.
    var standardIntegrityManager = new StandardIntegrityManagerV2();

    // 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 != null &&
        integrityTokenProviderOperation.Error.ErrorCode != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenProviderOperation.Error.ErrorCode);
        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, чтобы защититься от атак с вмешательством. Например, игра может передавать количество очков игрока на бэкенд-сервер, а ваш сервер должен убедиться, что эти данные не были изменены прокси-сервером. 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() {
    StandardIntegrityManagerV2.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 != null &&
        integrityTokenOperation.Error.ErrorCode != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenOperation.Error.ErrorCode);
        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.

В результате вы получите токен в виде обычного текста, содержащий оценки целостности.

Автоматическая защита от повторного воспроизведения

Чтобы предотвратить атаки повторного воспроизведения, Google Play автоматически блокирует многократное использование токенов целостности. При попытке повторно расшифровать один и тот же токен оценки будут удалены следующим образом:

  • Оценка распознавания устройства будет пустой.
  • Оценки целостности и лицензирования приложения будут иметь значение UNEVALUATED.
  • Любая из необязательных оценок, включенных в Play Console, будет иметь значение UNEVALUATED (или будет пустой, если это оценка с несколькими значениями).

Устранение проблем с оценкой с помощью подсказки Google Play (необязательно)

Получив оценку целостности, сервер может определить, что делать дальше. Если оценка указывает на проблему, например приложение не лицензировано, в него внесены изменения или устройство взломано, вы можете дать пользователям возможность устранить проблему самостоятельно.

Play Integrity API позволяет показывать диалоговое окно Google Play, в котором пользователю предлагается выполнить определенное действие, например скачать официальную версию приложения из Google Play.

Подробнее о том, как вызывать эти диалоговые окна из приложения на основе ответа сервера…