You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Credential Manager集成Google登录无法获取用户邮箱的问题求助

Android Credential Manager集成Google登录无法获取用户邮箱的问题求助

问题描述

我正在使用Android Credential Manager API实现“Sign In with Google”功能,登录成功后能获取到GoogleIdTokenCredential,但无法拿到用户的邮箱地址。解码JWT格式的idToken后发现,payload里的email字段是null,虽然我已经在Google Cloud Console里配置了./auth/userinfo.email、./auth/userinfo.profile和openid这几个scope。

相关代码

登录按钮Composable

@Composable
fun SignInWithGoogleButton(modifier: Modifier = Modifier) {
    val context = LocalContext.current
    val credentialManager = remember(context) { CredentialManager.create(context) }
    val coroutineScope = rememberCoroutineScope()
    val WEB_CLIENT_ID = "[MyId]"

    Button(
        onClick = {
            coroutineScope.launch {
                try {
                    val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = WEB_CLIENT_ID)
                        // add the nonce later
                        .build()
                    val request: GetCredentialRequest = GetCredentialRequest.Builder()
                        .addCredentialOption(signInWithGoogleOption)
                        .build()
                    val result = credentialManager.getCredential(
                        context = context,
                        request = request
                    )
                    handleSignInWithGoogleResult(result)
                } catch (e: GetCredentialException) {
                    Log.e("GoogleSignIn", "Credential Manager Sign in with Google failed: ${e.message}", e)
                } catch (e: Exception) {
                    Log.e("GoogleSignIn", "An unexpected error occurred: ${e.message}", e)
                }
            }
        }
    ) {
        Text("Sign In with Google")
    }
}

结果处理方法

private fun handleSignInWithGoogleResult(result: GetCredentialResponse) {
    val credential = result.credential
    when (credential) {
        is CustomCredential -> {
            if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
                try {
                    val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
                    val idToken = googleIdTokenCredential.idToken
                    val email = googleIdTokenCredential.id
                    val displayName = googleIdTokenCredential.displayName

                    Log.d("GoogleSignIn", "ID Token: $idToken")
                    Log.d("GoogleSignIn", "Email (from googleIdTokenCredential.id): $email")
                    Log.d("GoogleSignIn", "Display Name: $displayName")
                } catch (e: GoogleIdTokenParsingException) {
                    Log.e("GoogleSignIn", "Failed to parse Google ID token credential: ${e.message}", e)
                }
            } else {
                Log.e("GoogleSignIn", "Unexpected type of CustomCredential: ${credential.type}")
            }
        }
        else -> {
            Log.e("GoogleSignIn", "Unexpected type of credential: ${credential.type}")
        }
    }
}

依赖配置

implementation("androidx.credentials:credentials:1.5.0")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0")
implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1")

问题细节

  • 调用googleIdTokenCredential.id得到的是一个数字ID,不是邮箱地址
  • 解码idToken的JWT payload后,email字段为null
  • 已经在Google Cloud Console配置了正确的scope:./auth/userinfo.email、./auth/userinfo.profile、openid
  • 用两个不同的Gmail账号测试,结果一致

已尝试的方法

  1. 确认APP中的WEB_CLIENT_ID和Google Cloud Console里的完全匹配
  2. 确认OAuth同意屏幕已正确配置
  3. 搜索了Credential Manager和Google ID Token相关的问题,但没找到针对这个场景的明确解决方案

解决方案

这个问题我之前在集成Credential Manager时也碰到过,核心原因是你没有在客户端的登录请求中显式声明需要的scope——虽然你在Google Cloud Console里配置了允许的scope,但Google的认证服务需要客户端主动请求才会返回对应的用户数据(比如邮箱),这是OpenID Connect的安全设计要求。

具体要做两个关键修改:

  1. 在构建GetSignInWithGoogleOption时添加scope参数
    你需要用setScopes方法把需要的scope列表传进去,这样Google才会在ID Token中包含邮箱、用户昵称等信息:

    val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = WEB_CLIENT_ID)
        // 显式声明需要的scope
        .setScopes(listOf("openid", "email", "profile"))
        // 后续如果需要防重放攻击的nonce,在这里设置即可
        .build()
    
  2. 修正邮箱地址的获取方式
    你之前误用了googleIdTokenCredential.id来拿邮箱,这个字段其实是用户的Google唯一数字ID(对应JWT里的sub claim)。正确的邮箱字段是googleIdTokenCredential.email:

    // 修正后的结果处理代码片段
    val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
    val idToken = googleIdTokenCredential.idToken
    val userId = googleIdTokenCredential.id // 用户的数字唯一ID
    val email = googleIdTokenCredential.email // 正确的邮箱获取方式
    val displayName = googleIdTokenCredential.displayName
    
    Log.d("GoogleSignIn", "ID Token: $idToken")
    Log.d("GoogleSignIn", "User ID: $userId")
    Log.d("GoogleSignIn", "Email: $email")
    Log.d("GoogleSignIn", "Display Name: $displayName")
    

为什么之前的配置不生效?

Google Cloud Console里的scope是权限白名单,它只允许你的应用请求这些scope,但不会自动帮你在每次请求中包含它们。必须在客户端代码里明确声明,Google的认证服务才会把对应的数据打包到ID Token中返回给你。

验证方式

修改完成后,你可以再次解码idToken(用JWT解码工具比如jwt.io),检查payload里是否出现了email和email_verified字段,确认邮箱信息已经正确返回。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:28:01