关联的账号登录功能可让已将 Google 账号与您的服务相关联的用户一键登录。这可以改善 因为他们只需点击一下即可登录,无需再次进入, 用户名和密码进行登录。这还可以降低用户在您的服务中创建重复账号的几率。
“关联的账号登录”是以下账号的“一键登录”流程的一部分: Android。这意味着,如果您的应用 已启用一键式功能。
在本文档中,您将了解如何修改 Android 应用以支持 关联的账号登录。
工作原理
- 您可以在一键式登录流程中选择显示关联的账号。
- 如果用户已登录 Google 账号,并已将自己的 Google 账号与 就会返回一个 ID 令牌,用于关联 。
- 系统会向用户显示一键登录提示,并提供登录您 关联账号使用 Google 服务。
- 如果用户选择继续使用关联的账号,则用户的 ID 令牌 返回给您的应用您需要将此令牌与发送到 服务器识别已登录的用户。
设置
设置您的开发环境
在开发主机上获取最新的 Google Play 服务:
- 打开 Android SDK 管理器。
在 SDK Tools 下,找到 Google Play 服务。
如果这些软件包的状态未为“已安装”,请同时选中它们并点击 安装软件包。
配置您的应用
在项目级
build.gradle
文件中,添加 Google 的 Maven 制品库 在buildscript
和allprojects
部分中均有提供。buildscript { repositories { google() } } allprojects { repositories { google() } }
将“与 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 令牌中
iss
的值等于accounts.google.com
或https://accounts.google.com
。 - ID 令牌的过期时间 (
exp
) 未过。 - 如果您需要验证 ID 令牌是否代表 Google Workspace 或 Cloud
组织账号时,您可以查看
hd
声明,该声明表示托管的 用户的网域。将资源访问权限限制为只有 特定网域。缺少此声明即表示相应账号不属于 Google 托管的域名。
您可以使用 email
、email_verified
和 hd
字段来确定
Google 负责托管电子邮件地址,并对其具有权威性。如果 Google 拥有权威性,
该用户就是所知的合法账号所有者,您可以跳过密码或其他输入框
验证方法。
Google 具有权威性的情形:
email
的后缀为@gmail.com
,这是一个 Gmail 账号。email_verified
为 true 且hd
已设置,这是 G Suite 账号。
用户无需使用 Gmail 或 G Suite 即可注册 Google 账号。时间
email
不包含 @gmail.com
后缀且 hd
不存在,Google 未
建议使用权威凭据和密码或其他验证方法进行验证
用户。email_verified
可能为 true,因为 Google 最初验证了
创建 Google 账号后,该用户会拥有第三方的所有权,
后,电子邮件账号可能已更改。
我们强烈建议不要自行编写代码来执行这些验证步骤,
建议您使用适用于您平台的 Google API 客户端库,
JWT 库。对于开发和调试,您可以调用我们的 tokeninfo
验证端点。
使用 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
组织账号,您可以通过检查域名来验证 hd
所有权声明
由 Payload.getHostedDomain()
方法返回。
调用 tokeninfo 端点
为调试验证 ID 令牌签名的一种简单方法是
使用 tokeninfo
端点。调用此端点涉及
这个额外的网络请求会为您完成大部分的验证工作,
验证和载荷提取。不适合在生产环境中使用
因为请求可能会受到限制或出现间歇性错误。
如需使用 tokeninfo
端点验证 ID 令牌,请创建 HTTPS
POST 或 GET 请求发送到端点,并在
id_token
参数。
例如,要验证令牌“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 托管网域。