启用对 Google Play 游戏服务的服务器端访问

如果您的游戏使用后端服务器,我们建议您使用 Google 登录对玩家进行身份验证,并安全地将玩家的身份传递到后端服务器。这样一来,您的游戏就可以安全地检索玩家的身份和其他数据,而不会在通过设备时面临可能被篡改的风险。

在此场景中,您的游戏会提示玩家像往常一样登录 Google Play 游戏服务。当玩家成功登录后,GoogleSignInAccount 对象会包含一个特殊的一次性代码(称为服务器身份验证代码),并由客户端传递到服务器。然后,在服务器上,用服务器授权代码换取 OAuth 2.0 令牌,以供服务器用于调用 Google Play 游戏服务 API。

如需获得有关在游戏中添加登录功能的其他指导,请参阅 Android 游戏中的登录功能

如需查看详细的代码示例,了解如何使用 Google 登录功能对玩家进行身份验证,请参阅 GitHub 上的 clientserverskeleton 示例

如需离线访问,必须执行以下步骤:

  1. 在 Google Play 管理中心内:为您的游戏服务器创建凭据。凭据的 OAuth 客户端类型为“Web”(网络)。
  2. 在 Android 应用中:在登录过程中,请求获取服务器凭据的服务器授权代码,并将其传递给您的服务器。
  3. 在您的游戏服务器上:通过 Google 授权服务用服务器授权代码换取 OAuth 访问令牌,然后使用该访问令牌来调用 Play 游戏服务 REST API

准备工作

您需要先在 Google Play 管理中心中添加游戏,然后才能将 Google 登录功能集成到您的游戏中,如设置 Google Play 游戏服务中所述。

为您的游戏创建关联的服务器端 Web 应用

Google Play 游戏服务未针对网络游戏提供后端支持。但该服务为 Android 游戏的服务器提供了后端服务器支持。

如果您希望在服务器端应用中使用适用于 Google Play 游戏服务的 REST API,请按以下步骤操作:

  1. Google Play 管理中心关联的应用部分,为您的游戏创建关联的 Web 应用。请注意,launch_url 不会用于此流程,可以留空。
  2. 如需获取应用的凭据信息,请按以下步骤操作:
    1. 在 Google Play 管理中心内,从您的游戏中,点击游戏详情
    2. 向下滚动到 API 控制台项目部分,然后点击指向 API 控制台项目的链接。
    3. 在 Google API 控制台中,依次前往 API 和服务 > 凭据屏幕,下载您的 Web 应用的 client_secret.json 文件,并将其保存到您的服务器可以访问的位置。记录凭据的客户端 ID,以供日后参考。
  3. 重启服务器端应用,使其可以接受来自游戏客户端应用的请求。

在客户端上执行登录

GoogleSignInClient 类是用于检索当前已登录玩家的账号的主要入口点,如果玩家之前未在设备上登录您的应用,则还可用于让玩家登录。

如需创建登录客户端,请按以下步骤操作:

  1. 通过 GoogleSignInOptions 对象创建登录客户端。在用于配置登录的 GoogleSignInOptions.Builder 中,您必须指定 GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
  2. 您还必须指定您的游戏需要后端服务器的授权代码,方法是调用 GoogleSignInOptions.Builder.requestServerAuthCode() 方法,并将服务器的客户端 ID 作为参数。您稍后将检索该授权代码,以获取后端服务器上的访问令牌,如获取服务器授权代码中所述。
  3. 调用 GoogleSignIn.getClient() 方法并传入您之前配置的选项。如果调用成功,Google 登录 API 会返回 GoogleSignInClient 的实例。
  4. 获取 GoogleSignInClient 实例后,您应继续通过 activity 的 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

好友

请务必详阅好友指南,其中对好友功能做了更详细的说明。

成就

请务必查看成就指南,其中详细介绍了成就。

排行榜

请务必详阅排行榜指南,其中对排行榜做了更详细的说明。

  • 是否想获取游戏中所有记分板的列表?可以尝试调用 Leaderboards.list
  • 玩家是否已完成游戏?您可将其得分提交至 Scores.submit 并确定其是否创造了新的高分纪录。
  • 想要显示排行榜?从 Scores.list 获取数据并显示给用户。
  • 使用 Scores.listWindow 查找一系列接近用户最高得分的得分。
  • 如需获得更多有关玩家在特定排行榜中得分情况的信息(例如该玩家是否在所有玩家中名列前 12%),可以调用 Scores.get
  • 您是否正在调试游戏?可以尝试从管理 API 中调用 Scores.reset,重置该玩家在特定排行榜中的所有得分