使用新版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登录流程
- 旧的账户获取方法已废弃
需要解决三个问题:
- 如何从新版Credential Manager API的响应中获取有效的Account或GoogleSignInAccount?
- 是否存在适配Credential Manager的Google Calendar API替代使用方式?
- 是否需要手动将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)时,才需要手动兑换,步骤如下:
- 从
GoogleIdCredential中获取ID令牌:val idToken = credential?.idToken - 向谷歌令牌端点发送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

