“关联的账号登录”功能可为已将其 Google 账号关联到您的服务的用户启用使用 Google 一键登录功能。这改善了用户的体验,因为用户只需点击一下即可登录,而无需重新输入用户名和密码。还可降低用户在您的服务上创建重复帐号的可能性。
“关联的帐号登录”功能是 Android 的一键登录流程的一部分。这意味着,如果您的应用已启用一键式功能,则无需导入单独的库。
在本文档中,您将了解如何修改 Android 应用以支持关联的帐号登录。
运作方式
- 您可以在“一键登录”流程中选择显示关联的账号。
- 如果用户已在 Google 上登录,并已将其 Google 帐号与其服务帐号相关联,系统将为关联的帐号返回一个 ID 令牌。
- 系统会向用户显示一键登录提示,并提供使用关联的帐号登录您的服务的选项。
- 如果用户选择继续使用关联的帐号,则系统会将该用户的 ID 令牌返回给您的应用。您需要将此令牌与在第 2 步中发送到您服务器的令牌进行匹配,以识别已登录的用户。
设置
设置您的开发环境
在开发主机上获取最新的 Google Play 服务:
- 打开 Android SDK 管理器。
在 SDK Tools 下,找到 Google Play 服务。
如果这些软件包的状态均未为“已安装”,请同时选择它们并点击 Install Packages。
配置您的应用
在项目级
build.gradle
文件中,同时在buildscript
和allprojects
部分添加 Google 的 Maven 制品库。buildscript { repositories { google() } } allprojects { repositories { google() } }
将“Link with Google”API 的依赖项添加到模块的应用级 Gradle 文件(通常为
app/build.gradle
):dependencies { implementation 'com.google.android.gms:play-services-auth:21.2.0' }
修改您的 Android 应用以支持关联的账号登录
在“关联的账号登录”流程结束后,系统会向您的应用返回一个 ID 令牌。应在用户登录之前验证 ID 令牌的完整性。
以下代码示例详细介绍了检索、验证 ID 令牌以及随后让用户登录的步骤。
创建一个 activity 以接收 Sign-In 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") } });
构建登录请求
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()
启动登录待处理 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 令牌的完整性
如需验证令牌是否有效,请确保满足以下条件:
- ID 令牌由 Google 正确签名。使用 Google 的公钥(以 JWK 或 PEM 格式提供)来验证令牌的签名。这些密钥会定期轮替;请检查响应中的
Cache-Control
标头,以确定何时应再次检索它们。 - ID 令牌中
aud
的值等于应用的某个客户端 ID。通过进行这项检查,可以防止向恶意应用颁发的 ID 令牌用于访问应用后端服务器上同一用户的数据。 - ID 令牌中
iss
的值等于accounts.google.com
或https://accounts.google.com
。 - ID 令牌的过期时间 (
exp
) 尚未过去。 - 如果您需要验证该 ID 令牌代表的是 Google Workspace 或 Cloud 组织帐号,则可以检查
hd
声明,该声明指示用户的托管网域。仅允许特定网域的成员访问资源时,必须使用此方法。缺少此声明表示帐号不属于 Google 托管的网域。
我们强烈建议您使用适合您的平台的 Google API 客户端库或通用 JWT 库,而不是自行编写代码来执行这些验证步骤。对于开发和调试,您可以调用我们的 tokeninfo
验证端点。
使用 Google API 客户端库
建议使用 Java Google API 客户端库在生产环境中验证 Google ID 令牌。
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
声明。
调用 tokeninfo 端点
若要验证用于调试的 ID 令牌签名,一种简单的方法是使用 tokeninfo
端点。调用此端点涉及到一个额外的网络请求,该网络请求会为您执行大部分验证,而您在自己的代码中测试适当的验证和载荷提取时。它不适合在生产代码中使用,因为请求可能会受到限制或出现间歇性错误。
如需使用 tokeninfo
端点验证 ID 令牌,请向该端点发出 HTTPS POST 或 GET 请求,并在 id_token
参数中传递您的 ID 令牌。例如,要验证令牌“XYZ123”,可发出以下 GET 请求:
https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123
如果令牌已正确签名,并且 iss
和 exp
声明具有预期值,您会收到 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 托管网域。