Đăng nhập bằng tài khoản được liên kết dành cho Android

Tính năng Đăng nhập bằng tài khoản được liên kết sẽ bật tính năng Đăng nhập bằng một lần chạm bằng Google đối với những người dùng đã liên kết Tài khoản Google của họ với dịch vụ của bạn. Điều này giúp người dùng có trải nghiệm tốt hơn vì họ có thể đăng nhập bằng một lần nhấp mà không cần nhập lại tên người dùng và mật khẩu. Việc này cũng giúp giảm khả năng người dùng tạo tài khoản trùng lặp trên dịch vụ của bạn.

Tính năng Đăng nhập bằng tài khoản được liên kết hiện có trong quy trình Đăng nhập bằng một lần chạm dành cho Android. Điều này có nghĩa là bạn không cần nhập thư viện riêng nếu ứng dụng của bạn đã bật tính năng Một lần chạm.

Trong tài liệu này, bạn sẽ tìm hiểu cách sửa đổi ứng dụng Android để hỗ trợ tính năng Đăng nhập bằng tài khoản được liên kết.

Cách hoạt động

  1. Bạn chọn hiển thị các tài khoản được liên kết trong quy trình Đăng nhập bằng một lần chạm.
  2. Nếu người dùng đăng nhập trên Google và đã liên kết Tài khoản Google của họ với tài khoản của họ trên dịch vụ của bạn, thì một mã thông báo mã nhận dạng sẽ được trả về cho tài khoản được liên kết.
  3. Người dùng sẽ nhìn thấy lời nhắc đăng nhập bằng một lần chạm cùng với lựa chọn đăng nhập vào dịch vụ của bạn bằng tài khoản được liên kết.
  4. Nếu người dùng chọn tiếp tục với tài khoản được liên kết, mã thông báo mã nhận dạng của người dùng đó sẽ được trả về ứng dụng của bạn. Bạn so khớp mã này với mã thông báo đã được gửi đến máy chủ ở bước 2 để xác định người dùng đã đăng nhập.

Thiết lập

Thiết lập môi trường phát triển

Tải các dịch vụ mới nhất của Google Play trên máy chủ phát triển của bạn:

  1. Mở Trình quản lý SDK Android.
  1. Trong phần SDK Tools (Bộ công cụ SDK), hãy tìm Google Play Services (Dịch vụ Google Play).

  2. Nếu trạng thái của các gói này là chưa cài đặt, hãy chọn cả hai rồi nhấp vào Install Package (Cài đặt gói).

Định cấu hình ứng dụng

  1. Trong tệp build.gradle cấp dự án, hãy đưa kho lưu trữ Maven của Google vào cả hai phần buildscriptallprojects.

    buildscript {
        repositories {
            google()
        }
    }
    
    allprojects {
        repositories {
            google()
        }
    }
    
  2. Thêm các phần phụ thuộc cho API "Link with Google" (Liên kết với Google) vào tệp gradle cấp ứng dụng trong mô-đun của bạn, thường là app/build.gradle:

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

Sửa đổi ứng dụng Android để hỗ trợ tính năng Đăng nhập bằng tài khoản được liên kết

Vào cuối quy trình Đăng nhập bằng tài khoản được liên kết, một mã thông báo mã nhận dạng sẽ được trả về ứng dụng của bạn. Bạn phải xác minh tính toàn vẹn của mã thông báo mã nhận dạng trước khi đăng nhập cho người dùng.

Mã mẫu sau đây trình bày chi tiết các bước truy xuất, xác minh mã thông báo giá trị nhận dạng, sau đó đăng nhập cho người dùng.

  1. Tạo một hoạt động để nhận kết quả của ý định đăng nhập

    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. Tạo yêu cầu đăng nhập

    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. Chạy ý định đang chờ đăng nhập

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

Xác minh tính toàn vẹn của mã thông báo giá trị nhận dạng

如需验证令牌是否有效,请确保满足以下条件:

  • ID 令牌由 Google 正确签名。使用 Google 的公钥(以 JWKPEM 格式提供)来验证令牌的签名。这些密钥会定期轮替;请检查响应中的 Cache-Control 标头,以确定何时应再次检索它们。
  • ID 令牌中 aud 的值等于应用的某个客户端 ID。通过进行这项检查,可以防止向恶意应用颁发的 ID 令牌用于访问应用后端服务器上同一用户的数据。
  • ID 令牌中 iss 的值等于 accounts.google.comhttps://accounts.google.com
  • ID 令牌的过期时间 (exp) 尚未过去。
  • 如果您需要验证该 ID 令牌代表的是 Google Workspace 或 Cloud 组织帐号,则可以检查 hd 声明,该声明指示用户的托管网域。仅允许特定网域的成员访问资源时,必须使用此方法。缺少此声明表示帐号不属于 Google 托管的网域。

我们强烈建议您使用适合您的平台的 Google API 客户端库或通用 JWT 库,而不是自行编写代码来执行这些验证步骤。对于开发和调试,您可以调用我们的 tokeninfo 验证端点。

Sử dụng Thư viện ứng dụng API của Google

Bạn nên sử dụng Thư viện ứng dụng Java API của Google để xác thực mã thông báo mã nhận dạng của Google trong môi trường thực tế.

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.");
  }

Phương thức GoogleIdTokenVerifier.verify() xác minh chữ ký JWT, thông báo xác nhận quyền sở hữu aud, thông báo xác nhận quyền sở hữu iss và thông báo xác nhận quyền sở hữuexp.

Nếu cần xác thực rằng mã thông báo nhận dạng đại diện cho tài khoản của tổ chức Google Workspace hoặc Cloud, thì bạn có thể xác minh thông báo xác nhận quyền sở hữu hd bằng cách kiểm tra tên miền mà phương thức Payload.getHostedDomain() trả về.

调用 tokeninfo 端点

若要验证用于调试的 ID 令牌签名,一种简单的方法是使用 tokeninfo 端点。调用此端点涉及到一个额外的网络请求,该网络请求会为您执行大部分验证,而您在自己的代码中测试适当的验证和载荷提取时。它不适合在生产代码中使用,因为请求可能会受到限制或出现间歇性错误。

如需使用 tokeninfo 端点验证 ID 令牌,请向该端点发出 HTTPS POST 或 GET 请求,并在 id_token 参数中传递您的 ID 令牌。例如,要验证令牌“XYZ123”,可发出以下 GET 请求:

https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123

如果令牌已正确签名,并且 issexp 声明具有预期值,您会收到 HTTP 200 响应,其正文包含 JSON 格式的 ID 令牌声明。以下是示例响应:

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

如果您需要验证此 ID 令牌是否代表 Google Workspace 帐号,可以检查 hd 声明,该声明指示用户的托管网域。仅允许特定网域的成员访问资源时,必须使用此方法。缺少此声明表示帐号不属于 Google Workspace 托管网域。