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

使用新版Credential Manager API获取谷歌登录用户会话遇阻

问题描述

我正在更新应用,采用新版Credential Manager API结合Firebase实现谷歌登录认证,因为Jetpack Compose中旧方法GoogleSignIn.getSignedInAccountFromIntent已被废弃。

此前使用的旧代码:

GoogleSignIn.getSignedInAccountFromIntent(intent)

现在采用官方推荐的新版实现:

val googleIdOption = GetGoogleIdOption.Builder()
    .setFilterByAuthorizedAccounts(false)
    .setAutoSelectEnabled(false)
    .setServerClientId(BuildConfig.GOOGLE_SIGN_IN_SERVER_CLIENT_ID)
    .build()

val request = GetCredentialRequest.Builder()
    .addCredentialOption(googleIdOption)
    .build()

val credentialManager = CredentialManager.create(context)
val operation: Either<Throwable, GetCredentialResponse> = catch {
    credentialManager.getCredential(context, request)
}

登录流程能正常启动并返回GetCredentialResponse,但无法从响应中获取有效的GoogleSignInAccount(或类似会话对象)来访问Google Calendar API或其他谷歌认证服务。

我的使用场景是需要已登录的谷歌账户来配置Google Calendar API:

val mCredential = GoogleAccountCredential.usingOAuth2(
    context,
    arrayListOf(CalendarScopes.CALENDAR),
).setBackOff(ExponentialBackOff())

mCredential.selectedAccount = GoogleSignIn.getLastSignedInAccount(context)?.account

val transport = NetHttpTransport()
val jsonFactory = JacksonFactory.getDefaultInstance()

val calendarService = Calendar.Builder(transport, jsonFactory, mCredential)
    .setApplicationName(context.getString(R.string.app_name))
    .build()

但GoogleSignIn.getLastSignedInAccount(context)已被废弃,且找不到Credential Manager API中的替代方法,陷入两难:

  • 想用现代化的Credential Manager登录流程
  • 旧的账户获取方法已废弃

需要解决三个问题:

  1. 如何从新版Credential Manager API的响应中获取有效的Account或GoogleSignInAccount?
  2. 是否存在适配Credential Manager的Google Calendar API替代使用方式?
  3. 是否需要手动将ID令牌兑换为访问令牌?如果需要,具体如何操作?

解决方案

1. 从Credential Manager响应中获取账户信息

Credential Manager返回的GetCredentialResponse里包含的是GoogleIdCredential,无法直接获取GoogleSignInAccount(两套体系独立)。但可以通过以下方式获取Account对象:

先从响应中解析出用户邮箱,再通过AccountManager查询本地谷歌账户:

// 解析Credential Manager响应
val credential = operation.getOrNull()?.credential as? GoogleIdCredential
val userEmail = credential?.id

// 查询本地匹配的谷歌账户
val accountManager = AccountManager.get(context)
val googleAccounts = accountManager.getAccountsByType("com.google")
val targetAccount = googleAccounts.firstOrNull { it.name == userEmail }

注意:该方式依赖设备已添加的谷歌账户,若用户首次通过Credential Manager登录且未在设备添加账户,可能无法找到对应Account。

2. 适配Credential Manager的Google Calendar API使用方式

无需依赖GoogleSignInAccount,直接用Credential Manager获取的访问令牌即可构建Calendar服务:

步骤1:修改GetGoogleIdOption,请求Calendar权限

构建选项时添加Calendar的scope,确保返回有权限的访问令牌:

val googleIdOption = GetGoogleIdOption.Builder()
    .setFilterByAuthorizedAccounts(false)
    .setAutoSelectEnabled(false)
    .setServerClientId(BuildConfig.GOOGLE_SIGN_IN_SERVER_CLIENT_ID)
    .setScopes(listOf(CalendarScopes.CALENDAR)) // 新增Calendar权限
    .build()

步骤2:提取访问令牌

val credential = operation.getOrNull()?.credential as? GoogleIdCredential
val accessToken = credential?.accessToken

步骤3:自定义认证初始化器构建Calendar服务

替代原GoogleAccountCredential,直接在请求头注入访问令牌:

val transport = NetHttpTransport()
val jsonFactory = JacksonFactory.getDefaultInstance()

// 自定义认证逻辑
val authInitializer = HttpRequestInitializer { request ->
    request.headers.setAuthorization("Bearer $accessToken")
}

// 构建Calendar服务实例
val calendarService = Calendar.Builder(transport, jsonFactory, authInitializer)
    .setApplicationName(context.getString(R.string.app_name))
    .build()

3. ID令牌兑换访问令牌的说明

如果在GetGoogleIdOption中已正确设置scope,Credential Manager会直接返回包含访问令牌的GoogleIdCredential,无需手动兑换。

仅当你只请求了ID令牌(未设置scope)时,才需要手动兑换,步骤如下:

  1. 从GoogleIdCredential中获取ID令牌:val idToken = credential?.idToken
  2. 向谷歌令牌端点发送POST请求兑换:
val tokenUrl = "https://oauth2.googleapis.com/token"
val client = OkHttpClient()

val formBody = FormBody.Builder()
    .add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
    .add("assertion", idToken)
    .add("scope", CalendarScopes.CALENDAR)
    .build()

val request = Request.Builder()
    .url(tokenUrl)
    .post(formBody)
    .build()

val response = client.newCall(request).execute()
val responseBody = response.body?.string()
// 解析响应体提取access_token字段

不推荐此方式,直接在GetGoogleIdOption中设置scope更简洁可靠。


内容的提问来源于stack exchange,提问作者Bhavik Maradiya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 20:22:33