直接通过您的平台发起 Google 帐号关联。

关联帐号可以直接在移动应用中完成,这样您就可以让用户将您服务的帐号与 Google 帐号相关联。建立的关联可向 Google 授予用户同意共享的数据的访问权限。

用户可享有的优势包括:

  • 用户可以在您的应用中开始并完成帐号关联流程,这是他们熟悉的环境。
  • 用户无需登录凭据,因为已在设备和您的移动应用中通过身份验证。

开发者获得的好处包括:

  • 在移动应用中(例如,在用户设置中,在插页式广告上或用户登录移动应用后)宣传和启动帐号关联,以提高互动度和关联的帐号数量。
  • 转化率有所提高,因为用户可以完成的步骤比基于网络的标准 OAuth 流程少。
  • 实现您的平台 (Android) 链接的工程工作量很少,因为此流程已利用您现有的 OAuth2.0 实现(假设您已实现一个)。
  • 提升了用户流失率,因为用户无需重新输入登录凭据,用更少的步骤就能完成此流程。 在要求用户召回和输入登录凭据的流程中,流失率可能高达 80%。

运作方式

若要从您的平台建立关联,请按照下列步骤完成关联:

  1. 用户将在您的移动应用中点击 / 切换关联触发器。
  2. 用户选择要关联的 Google 帐号。
    1. 用户选择要关联设备上的现有 Google 账号,或者使用新账号登录
  3. 系统会向用户显示由 Google 托管的意见征求屏幕,用户必须同意继续或取消,才能停止关联流程。
  4. 系统会向用户显示同意屏幕,用户必须同意继续或取消,才能停止关联流程。
  5. 关联是在用户的服务上的帐号与其 Google 帐号之间建立的。

图 1. 通过平台流程进行关联

要求

如需在您的平台中实现 Link,您需要:

  • Android 应用。
  • 拥有、管理和维护支持 OAuth 2.0 授权代码流程的 OAuth 2.0 服务器。

初始设置

您必须先完成帐号关联注册流程,然后才能继续执行下面的步骤。

设置开发环境

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

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

  2. 如果这些软件包的状态均为“未安装”,请将其全部选中,然后点击 Install Packages(安装软件包)。

配置您的应用

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

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

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

通过平台流程建立关联,Google 会保存一个由您的服务提供的访问令牌。必须先征得用户同意,然后才能为用户返回令牌。

请按以下步骤操作,获取用户同意,并通过 Google Play 服务 SDK 返回授权代码令牌。

  1. 构建一个可以启动意见征求 activity 的 PendingIntent - 该意见征求由 Play Services API 启动。在调用该 API 时,您将需要提供 PendingIntent(为清楚起见,将称为 consentPendingIntent

    Kotlin

    // Build a PendingIntent that can launch the consent activity
    val consentPendingIntent = buildConsentPendingIntent()
    

    Java

    // Build a PendingIntent that can launch your consent activity
    PendingIntent consentPendingIntent =
              buildConsentPendingIntent();
    
  2. 创建相应的 activity 来处理意见征求 intent

    Kotlin

      class ConsentActivity : AppCompatActivity
    
      private fun onConsentAccepted() {
          // Obtain a token (for simplicity, we’ll ignore the async nature
          // of the following call)
          val token = getToken()
          val intent = Intent()
                      .putExtra(SaveAccountLinkingTokenRequest.EXTRA_TOKEN,
                                token)
          setResult(Activity.RESULT_OK, intent)
          finish()
      }
    
      private fun onConsentRejectedOrCanceled() {
          setResult(Activity.RESULT_CANCELED)
          finish()
      }
    

    Java

      public class ConsentActivity extends AppCompatActivity {
        ...
        private void onConsentAccepted() {
          // Obtain a token (for simplicity, we’ll ignore the async nature of
          // the following call
          String token = getToken();
          Intent intent = new Intent();
          intent.putExtra(SaveAccountLinkingTokenRequest.EXTRA_TOKEN, token);
          setResult(Activity.RESULT_OK, intent);
          finish();
        }
    
        private void onConsentRejectedOrCanceled() {
          setResult(Activity.RESULT_CANCELED, null);
          finish();
        }
     }
    
    

    我们假设如果用户接受或拒绝/取消了同意,系统会分别调用 onConsentAccpeted()onConsentRejectedOrCanceled() 方法。

  3. 创建用于保存令牌的请求,并传递在上文第 1 步中创建的 PendingIntent,以及其他配置参数。

    Kotlin

      // Create an ActivityResultLauncher which registers a callback for the
      // Activity result contract
      val activityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartIntentSenderForResult())
        { result ->
          if (result.resultCode == RESULT_OK) {
            // Successfully finished the flow and saved the token
          } else {
            // Flow failed, for example the user may have canceled the flow
          }
        }
    
      // Build token save request
      val request = SaveAccountLinkingTokenRequest.builder()
        .setTokenType(SaveAccountLinkingTokenRequest.TOKEN_TYPE_AUTH_CODE)
        .setConsentPendingIntent(consentPendingIntent)
        .setServiceId("service-id-of-and-defined-by-developer")
        //Set the scopes that the token is valid for on your platform
        .setScopes(scopes)
        .build()
    
       // Launch consent activity and retrieve token
       Identity.getCredentialSavingClient(this)
         .saveAccountLinkingToken(request)
         .addOnSuccessListener( saveAccountLinkingTokenResult -> {
            if (saveAccountLinkingTokenResult.hasResolution()) {
              val pendingIntent = saveAccountLinkingTokenResult
                                  .getPendingIntent()
              val intentSenderRequest = IntentSenderRequest
                                        .Builder(pendingIntent).build()
              activityResultLauncher.launch(intentSenderRequest)
            } else {
               // This should not happen, let’s log this
               Log.e(TAG, "Failed to save token");
            }
          })
          .addOnFailureListener(e -> Log.e(TAG, “Failed to save token”, e))
    

    Java

      // Create an ActivityResultLauncher which registers a callback for the
      // Activity result contract
      ActivityResultLauncher<IntentSenderRequest>
          activityResultLauncher =
          registerForActivityResult(new ActivityResultContracts
                                        .StartIntentSenderForResult(),
                                    result -> {
          if (result.getResultCode() == RESULT_OK) {
              // Successfully finished the flow and saved the token
          } else {
              // Flow failed, for example the user may have canceled the flow
          }
      });
    
     // Build token save request
     SaveAccountLinkingTokenRequest request =
        SaveAccountLinkingTokenRequest.builder()
            .setTokenType(
                SaveAccountLinkingTokenRequest.TOKEN_TYPE_AUTH_CODE)
            .setConsentPendingIntent(consentPendingIntent)
            .setServiceId("service-id-of-and-defined-by-developer")
            //Set the scopes that the token is valid for on your platform
            .setScopes(scopes)
            .build();
    
      // Launch consent activity and retrieve token
      Identity.getCredentialSavingClient(this)
          .saveAccountLinkingToken(request)
          .addOnSuccessListener(
              saveAccountLinkingTokenResult -> {
                if (saveAccountLinkingTokenResult.hasResolution()) {
                  // Launch the resolution intent
                  PendingIntent pendingIntent =
                      saveAccountLinkingTokenResult.getPendingIntent();
                  IntentSenderRequest intentSenderRequest =
                      new IntentSenderRequest.Builder(pendingIntent).build();
                  activityResultLauncher.launch(intentSenderRequest);
                } else {
                  // This should not happen, let’s log this
                  Log.e(TAG, "Failed to save token");
                }
              })
          .addOnFailureListener(e -> Log.e(TAG, "Failed to save token", e));
      ```
    

上述步骤会提示用户同意,并向 Google 返回一个授权代码。

最佳做法

  • 您的应用应通过按钮、切换开关或类似视觉元素向用户表明链接状态。

    图 1. 链接状态图片示例

  • 您应在链接成功(例如显示消息框)、触发切换开关状态更改或将用户重定向到单独的链接成功页面后通知用户。

  • 您应考虑提示应用内用户关联帐号,最好基于明确表明进行关联可为这些用户带来益处的信号。

  • 成功关联后,您应向用户展示关联的账号可执行哪些操作。例如,如果您刚刚关联了音乐在线播放服务,可以让 Google 助理播放音乐。

  • 让用户能够管理其关联的帐号,包括让用户解除关联。将他们引导至“与 Google 关联的帐号管理”页面,即 https://myaccount.google.com/accountlinking。

参考

Android Auth API 参考文档