Серверный доступ к игровым сервисам Google Play

Мы рекомендуем использовать PgsGamesSignInClient для аутентификации игроков и безопасной передачи идентификационных данных игрока на серверную часть. Это позволит вашей игре безопасно получать идентификационные данные игрока и другую информацию, исключая возможность несанкционированного доступа при передаче данных через устройство.

После успешной аутентификации игрока вы можете запросить специальный одноразовый код (называемый кодом авторизации сервера ) у Play Games Services v2 Native SDK (бета-версия), который клиент передает серверу. Затем на сервере код авторизации сервера обменивается на токен OAuth 2.0, который сервер может использовать для выполнения вызовов к API Google Play Games Services.

Дополнительные рекомендации по добавлению аутентификации в ваши игры см. в разделе «Аутентификация платформы» .

Для доступа в автономном режиме необходимо выполнить следующие шаги:

  1. В консоли Google Play: создайте учетные данные для вашего игрового сервера. Тип OAuth-клиента в учетных данных будет "web".
  2. В приложении для Android: В рамках аутентификации платформы запросите код авторизации сервера для учетных данных вашего сервера и передайте его на ваш сервер. PgsGamesSignInClient может запрашивать три области действия OAuth 2.0 при запросе доступа к веб-API Play Games Services на стороне сервера. Необязательные области действия: PGS_AUTH_SCOPE_EMAIL , PGS_AUTH_SCOPE_PROFILE и PGS_AUTH_SCOPE_OPENID . Две области действия по умолчанию: DRIVE_APPFOLDER и GAMES_LITE .
  3. На вашем игровом сервере: обменяйте код авторизации сервера на токен доступа OAuth, используя сервисы аутентификации Google, а затем используйте его для вызова REST API сервисов Play Games.

Прежде чем начать

Сначала вам нужно добавить свою игру в Google Play Console , как описано в разделе «Настройка служб Google Play Games для вашей игры».

Создайте веб-приложение на стороне сервера.

Сервисы Google Play Game не предоставляют серверную поддержку для веб-игр. Однако они предоставляют серверную поддержку для вашей игры на Android.

Если вы хотите использовать REST API для сервисов Google Play Games в своем серверном приложении, выполните следующие шаги:

  1. В консоли Google Play выберите игру.
  2. Перейдите в раздел «Службы Play Games» > «Настройка и управление» > «Конфигурация» .
  3. Выберите «Добавить учетные данные» , чтобы перейти на страницу добавления учетных данных . Выберите «Игровой сервер» в качестве типа учетных данных и перейдите к разделу «Авторизация» .
    1. Если на вашем игровом сервере уже есть идентификатор клиента OAuth, выберите его из выпадающего меню. После сохранения изменений перейдите к следующему разделу .
    2. Если у вас нет существующего идентификатора клиента OAuth для вашего игрового сервера, вы можете его создать.
      1. Нажмите «Создать OAuth-клиент» и перейдите по ссылке «Создать идентификатор OAuth-клиента» .
      2. Это перенаправит вас на страницу создания идентификатора клиента OAuth в Google Cloud Platform для вашего проекта, связанного с вашей игрой.
      3. Заполните форму на странице и нажмите «Создать». Обязательно укажите тип приложения как «Веб-приложение».
      4. Вернитесь в раздел «Авторизация» на странице «Добавить учетные данные» , выберите только что созданный клиент OAuth и сохраните изменения.

Получите код авторизации сервера.

Чтобы получить код авторизации сервера, который ваша игра может использовать в качестве токенов доступа на вашем бэкэнд-сервере:

  1. Вызовите функцию PgsGamesSignInClient_requestServerSideAccess с клиентской стороны.
    1. Убедитесь, что вы используете идентификатор клиента OAuth, зарегистрированный для вашего игрового сервера , а не идентификатор клиента OAuth вашего Android-приложения.
    2. (Необязательно) Если вашему игровому серверу требуется автономный доступ (длительный доступ с использованием токена обновления) к сервисам Play Games, вы можете установить параметр force_refresh_token в значение true.
  2. (Необязательно) В рамках аутентификации новые пользователи должны увидеть единый экран согласия на дополнительные области действия. После принятия согласия вы устанавливаете параметр PgsAuthScope scopes с областями действия OAuth PGS_AUTH_SCOPE_EMAIL , PGS_AUTH_SCOPE_PROFILE и PGS_AUTH_SCOPE_OPENID . Если пользователи отклоняют согласие, на сервер отправляются только две области действия по умолчанию DRIVE_APPFOLDER и GAMES_LITE .

    Экран согласия на добавление дополнительных областей действия OAuth.
    Экран согласия на добавление дополнительных областей действия OAuth. (нажмите для увеличения).

     // #include "google/games/pgs_games_sign_in_client.h"
     // 1. Define the Callback
     // This function is called when the server-side access request completes.
     // It provides the authorization code (on success) or an error (on failure).
     void OnServerSideAccessCallback(void* context, PgsError error, const char* serverAuthCode) {
         if (error == PgsError_Success) {
             if (serverAuthCode != nullptr) {
                 __android_log_print(ANDROID_LOG_INFO, "Games",
                     "Received Server Auth Code: %s", serverAuthCode);
                 // Send 'serverAuthCode' to your backend server immediately.
                 // Your server will exchange this code for an OAuth access token.
             }
         } else {
             __android_log_print(ANDROID_LOG_ERROR, "Games",
              "Failed to get server auth code. Error: %d", error);
         }
     }
     // 2. Define the Wrapper Function
     void RequestServerAccess(PgsGamesSignInClient* signInClient) {
         if (signInClient == nullptr) {
             return;
         }
         // This must match the "Web client ID" from your Google Cloud Console
         // (linked to your Play Console Game Server Credential).
         const char* SERVER_CLIENT_ID = "xxxx";
         // Set to 'true' if your server needs a Refresh Token (long-lived access).
         // Set to 'false' if you only need an Access Token (short-lived).
         bool forceRefreshToken = false;
         // Call the API
         PgsGamesSignInClient_requestServerSideAccess(
            signInClient,
            SERVER_CLIENT_ID,
            forceRefreshToken,
            OnServerSideAccessCallback, // The callback defined
            nullptr                     // User context (optional, passed to callback)
         );
     }
     // 3. Example Usage
     void TriggerSignInProcess(PgsGamesClient* gamesClient) {
          // Obtain the Sign-In Client from the main Games Client
          PgsGamesSignInClient* signInClient = PgsGamesClient_getSignInClient(gamesClient);
          RequestServerAccess(signInClient);
     }
     

  3. Отправьте токен авторизации OAuth на свой бэкэнд-сервер для обмена, проверки идентификатора игрока с помощью REST API сервисов Play Games и последующей аутентификации в вашей игре.

Отправьте код авторизации сервера.

Отправьте код авторизации сервера на свой бэкэнд-сервер для обмена на токены доступа и обновления. Используйте токен доступа для вызова API сервисов Play Games от имени игрока и, при необходимости, сохраните токен обновления для получения нового токена доступа по истечении срока действия предыдущего.

Для получения более подробной информации о том, как работают идентификаторы игроков, см. раздел «Идентификаторы игроков нового поколения» .

Приведённый ниже фрагмент кода показывает, как можно реализовать серверный код на языке программирования C++ для обмена кода аутентификации сервера на токены доступа.

Java

/**
 * Exchanges the authcode for an access token credential. The credential
 * is associated with the given player.
 *
 * @param authCode - the non-null authcode passed from the client.
 * @param player   - the player object which the given authcode is
 *                 associated with.
 * @return the HTTP response code indicating the outcome of the exchange.
 */
private int exchangeAuthCode(String authCode, Player player) {
try {

    // The client_secret.json file is downloaded from the Google Cloud
    // console. This is used to identify your web application. The
    // contents of this file shouldn't be shared.

    File secretFile = new File("client_secret.json");

    // If we don't have the file, we can't access any APIs, so return
    // an error.
    if (!secretFile.exists()) {
        log("Secret file : " + secretFile
                .getAbsolutePath() + "  does not exist!");
        return HttpServletResponse.SC_FORBIDDEN;
    }

    GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(
            JacksonFactory.getDefaultInstance(), new
            FileReader(secretFile));

    // Extract the application ID of the game from the client ID.
    String applicationId = extractApplicationId(clientSecrets
            .getDetails().getClientId());

    GoogleTokenResponse tokenResponse =
            new GoogleAuthorizationCodeTokenRequest(
            HTTPTransport,
            JacksonFactory.getDefaultInstance(),
            "https://oauth2.googleapis.com/token",
            clientSecrets.getDetails().getClientId(),
            clientSecrets.getDetails().getClientSecret(),
            authCode,
            "")
            .execute();

    TokenVerifier(tokenResponse);

    log("hasRefresh == " + (tokenResponse.getRefreshToken() != null));
    log("Exchanging authCode: " + authCode + " for token");
    Credential credential = new Credential
            .Builder(BearerToken.authorizationHeaderAccessMethod())
            .setJsonFactory(JacksonFactory.getDefaultInstance())
            .setTransport(HTTPTransport)
            .setTokenServerEncodedUrl("https://www.googleapis.com/oauth2/v4/token")
            .setClientAuthentication(new HttpExecuteInterceptor() {
                @Override
                public void intercept(HttpRequest request)
                        throws IOException {
                        }
            })
            .build()
            .setFromTokenResponse(tokenResponse);

    player.setCredential(credential);

    // Now that we have a credential, we can access the Games API.
    PlayGamesAPI api = new PlayGamesAPI(player, applicationId,
            HTTPTransport, JacksonFactory.getDefaultInstance());

    // Call the verify method, which checks that the access token has
    // access to the Games API, and that the Player ID used by the
    // client matches the playerId associated with the accessToken.
    boolean ok = api.verifyPlayer();

    // Call a Games API on the server.
    if (ok) {
        ok = api.updatePlayerInfo();
        if (ok) {
            // persist the player.
            savePlayer(api.getPlayer());
        }
    }

    return ok ? HttpServletResponse.SC_OK :
            HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

  } catch (IOException e) {
    e.printStackTrace();
  }
  return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}

Получить области действия OAuth можно с помощью клиентских библиотек Google API в Java или Python, чтобы получить объект GoogleIdTokenVerifier . Следующий фрагмент кода демонстрирует реализацию на языке программирования Java.

Java

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;

/**
 * Gets the GoogleIdTokenVerifier object and additional OAuth scopes.
 * If additional OAuth scopes are not requested, the idToken will be null.
 *
 * @param tokenResponse - the tokenResponse passed from the exchangeAuthCode
 *                        function.
 *
 **/

void TokenVerifier(GoogleTokenResponse tokenResponse) {

    string idTokenString = tokenResponse.getIdToken();

    GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
        // Specify the WEB_CLIENT_ID of the app that accesses the backend:
        .setAudience(Collections.singletonList(WEB_CLIENT_ID))
        // Or, if multiple clients access the backend:
        //.setAudience(Arrays.asList(WEB_CLIENT_ID_1, WEB_CLIENT_ID_2, WEB_CLIENT_ID_3))
        .build();

    GoogleIdToken idToken = verifier.verify(idTokenString);

    // The idToken can be null if additional OAuth scopes are not requested.
    if (idToken != null) {
        Payload payload = idToken.getPayload();

    // Print user identifier
    String userId = payload.getSubject();
    System.out.println("User ID: " + userId);

    // Get profile information from payload
    String email = payload.getEmail();
    boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
    String name = (String) payload.get("name");
    String pictureUrl = (String) payload.get("picture");
    String locale = (String) payload.get("locale");
    String familyName = (String) payload.get("family_name");
    String givenName = (String) payload.get("given_name");

    // This ID is unique to each Google Account, making it suitable for use as
    // a primary key during account lookup. Email is not a good choice because
    // it can be changed by the user.
    String sub = payload.getSubject();

    // Use or store profile information
    // ...

    } else {
      System.out.println("Invalid ID token.");
    }
}

Вызов REST API с сервера

Полное описание доступных вызовов API см. в разделе REST API для сервисов Google Play Games .

Примеры полезных вызовов REST API включают следующее:

Игрок

Хотите получить данные для аутентификации по ID и профилю игрока? Вызовите метод Players.get, указав в качестве ID значение 'me' .

Достижения

Подробности смотрите в руководстве по достижениям .

  • Чтобы получить список текущих достижений, перейдите по ссылке AchievementDefinitions.list .

  • Добавьте к этому обращение к Achievements.list , чтобы узнать, какие достижения игрок разблокировал.

  • Чтобы разблокировать достижение игрока, вызовите команду Achievements.unlock .

  • Используйте функцию Achievements.increment , чтобы сообщить о ходе выполнения достижения и узнать, разблокировал ли игрок его.

  • Если вы отлаживаете игру, которая ещё не вышла в производство, вы можете вызвать методы Achievements.reset или Achievements.resetAll из API управления, чтобы сбросить достижения до их исходного состояния.