Google Play 게임즈 서비스에 대한 서버 측 액세스 사용 설정

게임에서 백엔드 서버를 사용하는 경우 Google 로그인을 사용하여 플레이어를 인증하고 플레이어의 ID를 백엔드 서버에 안전하게 전달하는 것이 좋습니다. 또한 기기를 통과하는 동안 잠재적인 조작에 노출되지 않고 게임이 플레이어의 ID 및 기타 데이터를 안전하게 검색할 수 있습니다.

이 시나리오에서는 게임에서 플레이어에게 평소와 같이 Google Play 게임즈 서비스에 로그인하라는 메시지를 표시합니다. 플레이어가 로그인하면 GoogleSignInAccount 객체에 클라이언트가 서버에 전달하는 특수한 일회용 코드(서버 인증 코드라고 함)가 포함됩니다. 그런 다음 서버에서 서버 인증 코드를 서버가 Google Play 게임즈 서비스 API를 호출하는 데 사용할 수 있는 OAuth 2.0 토큰으로 교환합니다.

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

Google 로그인을 사용하여 플레이어를 인증하는 방법을 보여주는 자세한 코드 샘플은 GitHub의 clientserverskeleton 샘플을 참고하세요.

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

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

시작하기 전에

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

게임에 연결된 서버 측 웹 애플리케이션 만들기

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

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

  1. Google Play Console연결된 앱 섹션에서 게임에 연결된 웹 앱을 만듭니다. launch_url는 이 흐름에 사용되지 않으며 비워 둘 수 있습니다.
  2. 앱의 사용자 인증 정보는 다음 단계에 따라 가져올 수 있습니다.
    1. Google Play Console의 게임에서 게임 세부정보를 클릭합니다.
    2. API 콘솔 프로젝트 섹션으로 아래로 스크롤하여 API 콘솔 프로젝트 링크를 클릭합니다.
    3. Google API 콘솔의 API 및 서비스 > 사용자 인증 정보 화면에서 웹 앱의 client_secret.json 파일을 다운로드하고 서버가 액세스할 수 있는 위치에 저장합니다. 나중에 참조할 수 있도록 사용자 인증 정보의 클라이언트 ID를 기록합니다.
  3. 서버 측 앱이 게임의 클라이언트 앱의 요청을 수락할 준비가 되도록 다시 시작합니다.

클라이언트에서 로그인 수행

GoogleSignInClient 클래스는 현재 로그인한 플레이어의 계정을 가져오고, 이전에 기기의 앱에서 로그인하지 않은 경우 플레이어를 로그인하는 기본 진입점입니다.

로그인 클라이언트를 만들려면 다음 단계를 따르세요.

  1. GoogleSignInOptions 객체를 통해 로그인 클라이언트를 만듭니다. GoogleSignInOptions.Builder에서 로그인을 구성하려면 GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN를 지정해야 합니다.
  2. 또한 서버의 클라이언트 ID를 매개변수로 사용하여 GoogleSignInOptions.Builder.requestServerAuthCode() 메서드를 호출하여 게임에 백엔드 서버의 인증 코드가 필요하다고 지정해야 합니다. 서버 인증 코드 가져오기에 설명된 대로 나중에 백엔드 서버에서 액세스 토큰의 인증 코드를 검색합니다.
  3. GoogleSignIn.getClient() 메서드를 호출하고 이전에 구성한 옵션을 전달합니다. 호출이 성공하면 Google Sign-In API가 GoogleSignInClient 인스턴스를 반환합니다.
  4. GoogleSignInClient 인스턴스를 가져온 후에는 숨김 로그인 실행에 설명된 대로 활동의 onResume()에서 플레이어를 자동으로 로그인해야 합니다.

예를 들면 다음과 같습니다.

private static final int RC_SIGN_IN = 9001;
private GoogleSignInClient mGoogleSignInClient;

private void startSignInForAuthCode() {

  // Client ID for your backend server.
  String webClientId = getString(R.string.webclient_id);

  GoogleSignInOptions signInOption = new
      GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
      .requestServerAuthCode(webClientId)
      .build();

  GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOption);
  Intent intent = signInClient.getSignInIntent();
  startActivityForResult(intent, RC_SIGN_IN);
}

서버 인증 코드 가져오기

게임이 백엔드 서버의 액세스 토큰에 사용할 수 있는 서버 인증 코드를 가져오려면 플레이어 로그인이 성공하면 Google 로그인이 반환하는 GoogleSignInAccount 객체에서 getServerAuthCode() 메서드를 호출합니다.

예를 들면 다음과 같습니다.

// Auth code to send to backend server.
private String mServerAuthCode;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == RC_SIGN_IN) {
    GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
    if (result.isSuccess()) {
      mServerAuthCode = result.getSignInAccount().getServerAuthCode();
    } else {
      String message = result.getStatus().getStatusMessage();
      if (message == null || message.isEmpty()) {
        message = getString(R.string.signin_other_error);
      }
      new AlertDialog.Builder(this).setMessage(message)
          .setNeutralButton(android.R.string.ok, null).show();
    }
  }
}

서버에서 서버 인증 코드를 액세스 토큰으로 교환

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

다음 코드 스니펫은 Java 프로그래밍 언어로 서버 측 코드를 구현하여 서버 인증 코드를 액세스 토큰으로 교환하는 방법을 보여줍니다. 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;
}

로그인한 플레이어를 대신하여 백엔드 서버에서 Google API에 액세스하는 방법을 자세히 알아보려면 서버 측 액세스 사용 설정을 참고하세요.

플레이어 로그아웃 처리

플레이어를 게임에서 로그아웃하려면 GoogleSignInClient에서 signOut() 메서드를 호출합니다. 코드 스니펫 예시는 플레이어 로그아웃을 참고하세요.

서버에서 REST API 호출

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

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

플레이어

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

친구

친구에 대한 자세한 설명이 나와 있는 친구 가이드를 꼼꼼히 읽어보세요.

  • 플레이어의 친구 목록을 가져오고 싶으신가요? 'friends_all'collection으로 사용하여 Players.list를 호출합니다.
  • 친구 목록에 액세스할 수 있는지 확인 me에 대해 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을 호출하여 특정 리더보드에서 해당 플레이어의 모든 점수를 재설정해 보세요.