关联帐号登录 (Android)

关联的账号登录功能可让已将 Google 账号与您的服务相关联的用户一键登录。这样可以改善用户体验,因为用户只需点击一下即可登录,而无需重新输入用户名和密码。这还可以降低用户在您的服务中创建重复账号的几率。

关联的账号登录功能是 Android 的一键登录流程的一部分。这意味着,如果您的应用已启用“一键式”功能,则无需导入单独的库。

在本文档中,您将了解如何修改 Android 应用以支持关联的账号登录。

工作原理

  1. 您可以在一键式登录流程中选择显示关联的账号。
  2. 如果用户已登录 Google,并已将其 Google 账号与您服务中的账号相关联,系统会针对关联的账号返回 ID 令牌。
  3. 系统会向用户显示一键式登录提示,其中包含使用关联的账号登录您的服务的选项。
  4. 如果用户选择继续使用关联的账号,系统会将用户的 ID 令牌返回到您的应用。您可以将此令牌与在第 2 步中发送到服务器的令牌进行匹配,以识别已登录的用户。

设置

设置您的开发环境

在开发主机上获取最新的 Google Play 服务:

  1. 打开 Android SDK 管理器
  1. SDK Tools 下,找到 Google Play 服务

  2. 如果这些软件包的状态不是“已安装”,请同时选择这两个软件包,然后点击安装软件包

配置您的应用

  1. 在项目级 build.gradle 文件中,同时在 buildscriptallprojects 两个部分中添加 Google 的 Maven 代码库。

    buildscript {
        repositories {
            google()
        }
    }
    
    allprojects {
        repositories {
            google()
        }
    }
    
  2. 将“与 Google 关联”API 的依赖项添加到模块的应用级 Gradle 文件(通常为 app/build.gradle):

    dependencies {
      implementation 'com.google.android.gms:play-services-auth:21.3.0'
    }
    

修改您的 Android 应用以支持关联的账号登录

在关联的账号登录流程结束时,系统会将 ID 令牌返回到您的应用。在让用户登录之前,应先验证 ID 令牌的完整性。

以下代码示例详细介绍了检索、验证 ID 令牌以及随后登录用户的步骤。

  1. 创建一个 activity 来接收登录 intent 的结果

    Kotlin

      private val activityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartIntentSenderForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
          try {
            val signInCredentials = Identity.signInClient(this)
                                    .signInCredentialFromIntent(result.data)
            // Review the Verify the integrity of the ID token section for
            // details on how to verify the ID token
            verifyIdToken(signInCredential.googleIdToken)
          } catch (e: ApiException) {
            Log.e(TAG, "Sign-in failed with error code:", e)
          }
        } else {
          Log.e(TAG, "Sign-in failed")
        }
      }
    

    Java

      private final ActivityResultLauncher<IntentSenderResult>
        activityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(),
        result -> {
        If (result.getResultCode() == RESULT_OK) {
            try {
              SignInCredential signInCredential = Identity.getSignInClient(this)
                             .getSignInCredentialFromIntent(result.getData());
              verifyIdToken(signInCredential.getGoogleIdToken());
            } catch (e: ApiException ) {
              Log.e(TAG, "Sign-in failed with error:", e)
            }
        } else {
            Log.e(TAG, "Sign-in failed")
        }
    });
    
  2. 构建登录请求

    Kotlin

    private val tokenRequestOptions =
    GoogleIdTokenRequestOptions.Builder()
      .supported(true)
      // Your server's client ID, not your Android client ID.
      .serverClientId(getString("your-server-client-id")
      .filterByAuthorizedAccounts(true)
      .associateLinkedAccounts("service-id-of-and-defined-by-developer",
                               scopes)
      .build()
    

    Java

     private final GoogleIdTokenRequestOptions tokenRequestOptions =
         GoogleIdTokenRequestOptions.Builder()
      .setSupported(true)
      .setServerClientId("your-service-client-id")
      .setFilterByAuthorizedAccounts(true)
      .associateLinkedAccounts("service-id-of-and-defined-by-developer",
                                scopes)
      .build()
    
  3. 启动“登录待处理”intent

    Kotlin

     Identity.signInClient(this)
        .beginSignIn(
      BeginSignInRequest.Builder()
        .googleIdTokenRequestOptions(tokenRequestOptions)
      .build())
        .addOnSuccessListener{result ->
          activityResultLauncher.launch(result.pendingIntent.intentSender)
      }
      .addOnFailureListener {e ->
        Log.e(TAG, "Sign-in failed because:", e)
      }
    

    Java

     Identity.getSignInClient(this)
      .beginSignIn(
        BeginSignInRequest.Builder()
          .setGoogleIdTokenRequestOptions(tokenRequestOptions)
          .build())
      .addOnSuccessListener(result -> {
        activityResultLauncher.launch(
            result.getPendingIntent().getIntentSender());
    })
    .addOnFailureListener(e -> {
      Log.e(TAG, "Sign-in failed because:", e);
    });
    

验证 ID 令牌的完整性

使用 Google API 客户端库

在生产环境中验证 Google ID 令牌时,建议使用 Java Google API 客户端库

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;

  ...

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

  // (Receive idTokenString by HTTPS POST)

  GoogleIdToken idToken = verifier.verify(idTokenString);
  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");

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

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

GoogleIdTokenVerifier.verify() 方法会验证 JWT 签名、aud 声明、iss 声明和 exp 声明。

如果您需要验证 ID 令牌是否代表 Google Workspace 或 Cloud 组织账号,可以通过检查 Payload.getHostedDomain() 方法返回的域名来验证 hd 声明。