Google Play 게임즈 서비스에 대한 서버 측 액세스

플레이어를 인증하고 플레이어의 ID를 백엔드 서버에 안전하게 전달하는 것이 좋습니다. 이렇게 하면 기기를 거치는 동안 잠재적인 조작에 노출되지 않고 게임이 플레이어의 ID와 기타 데이터를 안전하게 가져올 수 있습니다.

이 시나리오에서는 플레이어가 성공적으로 로그인하면 Play 게임즈 서비스 v2 SDK에서 특수한 일회용 코드(서버 인증 코드라고 함)를 요청할 수 있습니다. 이 코드는 클라이언트가 서버로 전달합니다. 그런 다음 서버에서 서버 인증 코드를 서버가 Google Play Games Services API를 호출하는 데 사용할 수 있는 OAuth 2.0 토큰으로 교환합니다.

게임에 로그인을 추가하는 방법을 자세히 알아보려면 Android 게임 로그인을 참고하세요.

오프라인 액세스에는 다음 단계가 필요합니다.

  1. Google Play Console: 게임 서버의 사용자 인증 정보를 만듭니다. 사용자 인증 정보의 OAuth 클라이언트 유형은 '웹'입니다.
  2. Android 앱: 로그인의 일환으로 서버의 사용자 인증 정보 서버 인증 코드를 요청하여 서버에 전달합니다.
  3. 게임 서버: Google 인증 서비스를 사용하여 서버 인증 코드를 OAuth 액세스 토큰으로 교환한 후 이를 사용하여 Play 게임즈 서비스 REST API를 호출합니다.

시작하기 전에

먼저 Google Play 게임즈 서비스 설정에 설명된 대로 Google Play Console에서 게임을 추가하고 Play 게임즈 서비스 로그인을 게임과 통합해야 합니다.

서버 측 웹 앱 만들기

Google Play 게임즈 서비스는 웹 게임에 백엔드 지원을 제공하지 않습니다. 그러나 Android 게임 서버에는 백엔드 서버 지원을 제공합니다.

서버 측 앱에서 Google Play 게임즈 서비스용 REST API를 사용하려면 다음 단계를 따르세요.

  1. Google Play Console의 게임에서 Play 게임즈 서비스 > 설정 및 관리 > 구성으로 이동합니다.
  2. 사용자 인증 정보 추가를 선택하여 사용자 인증 정보 추가 페이지로 이동합니다. 게임 서버를 사용자 인증 정보 유형으로 선택하고 승인 섹션으로 이동합니다.
    1. 게임 서버에 이미 OAuth 클라이언트 ID가 있으면 드롭다운 메뉴에서 이 ID를 선택합니다. 변경사항을 저장한 후 다음 섹션으로 이동합니다.
    2. 게임 서버에 기존 OAuth 클라이언트 ID가 없으면 새로 만들 수 있습니다.
      1. OAuth 클라이언트 만들기를 클릭하고 OAuth 클라이언트 ID 만들기 링크를 이용합니다.
      2. 그러면 게임과 연결된 Cloud Platform 프로젝트를 위한 Google Cloud Platform의 OAuth 클라이언트 ID 만들기 페이지로 이동합니다.
      3. 페이지 양식을 작성하고 만들기를 클릭합니다. 애플리케이션 유형을 웹 애플리케이션으로 설정해야 합니다.
      4. 사용자 인증 정보 추가 페이지의 승인 섹션으로 돌아가 새로 생성된 OAuth 클라이언트를 선택하고 변경사항을 저장합니다.

서버 인증 코드 가져오기

게임이 백엔드 서버의 액세스 토큰에 사용할 수 있는 서버 인증 코드를 가져오려면 다음 단계를 따르세요.

  1. 클라이언트에서 requestServerSideAccess를 호출합니다.

    1. Android 애플리케이션의 OAuth 클라이언트 ID가 아니라 게임 서버에 등록된 OAuth 클라이언트 ID를 사용해야 합니다.
    2. (선택사항) 게임 서버에 Play 게임즈 서비스로의 오프라인 액세스(갱신 토큰을 사용한 장기 지속 액세스)가 필요하면 forceRefreshToken 매개변수를 true로 설정하면 됩니다.
    GamesSignInClient gamesSignInClient = PlayGames.getGamesSignInClient(this);
    gamesSignInClient
      .requestServerSideAccess(OAUTH_2_WEB_CLIENT_ID, /* forceRefreshToken= */ false)
      .addOnCompleteListener( task -> {
        if (task.isSuccessful()) {
          String serverAuthToken = task.getResult();
          // Send authentication code to the backend game server to be
          // exchanged for an access token and used to verify the player
          // via the Play Games Services REST APIs.
        } else {
          // Failed to retrieve authentication code.
        }
    });
    
  2. OAuth 인증 코드 토큰을 백엔드 서버로 보내 교환할 수 있도록 하고 플레이어 ID를 Play 게임즈 서비스 REST API에 대해 확인한 다음 게임에 인증합니다.

서버 인증 코드 전송

서버 인증 코드를 백엔드 서버로 전송하여 액세스 및 갱신 토큰으로 교환합니다. 액세스 토큰을 사용하여 플레이어를 대신해 Play Games Services API를 호출합니다. 선택적으로 갱신 토큰을 저장하여 액세스 토큰이 만료되면 새 액세스 토큰을 획득할 수 있습니다.

다음 코드 스니펫은 자바 프로그래밍 언어로 서버 측 코드를 구현하여 서버 인증 코드를 액세스 토큰으로 교환하는 방법을 보여줍니다. clientserverskeleton 샘플 앱을 사용합니다.

/**
 * Exchanges the authcode for an access token credential.  The credential
 * is the 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 API
    // console.  This is used to identify your web application.  The
    // contents of this file should not 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();

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

서버에서 REST API 호출

사용 가능한 API 호출에 관한 자세한 내용은 Google Play 게임즈 서비스용 REST API를 참고하세요.

유용할 수 있는 REST API 호출의 예는 다음과 같습니다.

플레이어

로그인한 플레이어의 ID와 프로필 데이터를 가져오고 싶은가요? 'me'를 ID로 사용하여 [Players.get][]을 호출합니다.

친구

자세한 내용은 친구 가이드를 참고하세요.

  • 플레이어의 친구 목록을 가져오려면 friends_allcollection으로 사용하여 Players.list를 호출합니다.

  • 친구 목록에 액세스할 수 있는지 확인하려면 meplayerID로 사용하여 Players.get을 호출하고 응답에서 profileSettings.friendsListVisibility 필드를 확인합니다.

업적

자세한 내용은 업적 가이드를 참고하세요.

  • 현재 업적 목록을 가져오려면 AchievementDefinitions.list를 호출합니다.

  • 이를 Achievements.list 호출과 결합하여 플레이어가 달성한 업적을 확인합니다.

  • Achievements.unlock을 호출하여 플레이어 업적을 잠금 해제합니다.

  • Achievements.increment를 호출하여 업적 진행 상황을 보고하고 플레이어가 업적을 달성했는지 확인합니다.

  • 프로덕션에 도달하지 않은 게임을 디버깅하는 경우 Management API에서 Achievements.reset 또는 Achievements.resetAll을 호출하여 업적을 원래 상태로 재설정할 수 있습니다.

리더보드

자세한 내용은 리더보드 가이드를 참고하세요.

  • 게임의 모든 스코어보드 목록을 원하나요? Leaderboards.list를 호출하세요.

  • 플레이어가 게임을 완료하면 Scores.submit에 점수를 제출하고 새로운 최고점수인지 확인할 수 있습니다.

  • 리더보드를 표시하려면 Scores.list에서 데이터를 가져와 사용자에게 표시합니다.

  • Scores.listWindow를 사용하여 사용자의 최고점수에 근접한 다양한 점수를 확인하세요.

  • 특정 리더보드에서 플레이어의 점수에 관한 자세한 정보를 보려면(예를 들어 전체 플레이어 중 상위 12%에 속한 경우) Scores.get을 호출하세요.

  • 게임을 디버깅하는 경우 Management API에서 Scores.reset을 호출하여 특정 리더보드에서 플레이어의 모든 점수를 재설정할 수 있습니다.