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

Android下如何获取可自动刷新AccessToken的Google Drive API OAuth2凭证?

问题

如何获取一个可在AccessToken过期后自动刷新的OAuth2凭证对象,以访问Google Drive API?

已完成操作

我遵循Google的最新建议,在Android上通过Credential Manager完成身份验证,再通过Google Identity授权来访问Google Drive API。当前身份验证代码如下:

private fun authorizeGoogleDrive() {
    val requestedScopes = listOf(Scope(DriveScopes.DRIVE_READONLY))
    val authorizationRequest = AuthorizationRequest.Builder()
        .setRequestedScopes(requestedScopes)
        .build()
    Identity.getAuthorizationClient(this)
        .authorize(authorizationRequest)
        .addOnSuccessListener { authorizationResult ->
            if (authorizationResult.hasResolution()) {
                val pendingIntent = authorizationResult.pendingIntent
                try {
                    val intentSenderRequest = pendingIntent?.let { IntentSenderRequest.Builder(it.intentSender).build() }
                    intentSenderRequest?.let { authorizeGoogleDriveResultReceiver.launch(intentSenderRequest) }
                } catch (e: IntentSender.SendIntentException) {
                    Log.e(LOG, "SignInActivity/authorizeGoogleDrive Couldn't start Authorization UI: " + e.localizedMessage)
                }
            } else {
              
              buildDriveService(authorizationResult)
              
            }
        }
}

private val authorizeGoogleDriveResultReceiver = registerForActivityResult(
    ActivityResultContracts.StartIntentSenderForResult()
) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val authorizationResult: AuthorizationResult = Identity.getAuthorizationClient(
            this
        ).getAuthorizationResultFromIntent(result.data)

        buildDriveService(authorizationResult)

    } else {
        Log.e(LOG, "SignInActivity/authorizeGoogleDriveResultReceiver result.resultCode != Activity.RESULT_OK")
    }
}


fun buildDriveService(authorizationResult: AuthorizationResult) {
    val accessToken = AccessToken(authorizationResult.accessToken, Date(System.currentTimeMillis() + 3600 * 1000))
    val credential = GoogleCredentials.create(accessToken)
    driveService = Drive.Builder(
        GoogleNetHttpTransport.newTrustedTransport(),
        GsonFactory.getDefaultInstance(),
        HttpCredentialsAdapter(credential)
    )
        .setApplicationName(app_name)
        .build()

}

访问起初正常(持续约60分钟),但之后使用driveService对象时出现以下异常:

OAuth2Credentials instance does not support refreshing the access token. An instance with a new access token should be used, or a derived type that supports refreshing.

更奇怪的是:重启应用后,即使再次调用Identity.getAuthorizationClient(this).authorize(authorizationRequest),我始终获取到同一个accessToken。

根据com.google.auth.oauth2.GoogleCredentials(继承自OAuth2Credentials)的文档,该类不支持刷新accessToken。我尝试将GoogleCredentials替换为com.google.auth.oauth2.UserCredentials,希望该类支持自动刷新token,但使用其Builder时始终出现空指针异常,且该Builder需要传入refreshToken:

val userCredentials = UserCredentials.newBuilder()
    .setClientId(app_name)
    .setAccessToken(accessToken)
    .setRefreshToken(refreshToken)
    .build()

但我没有refreshToken,通过Identity.getAuthorizationClient(this).authorize(authorizationRequest)获取的authorizationResult对象中仅包含accessToken字段,没有refreshToken。

解决方案

1. 开启离线访问权限以获取RefreshToken

Google Identity的授权请求需要明确开启离线访问,才能拿到refreshToken。修改authorizeGoogleDrive方法中的请求构建逻辑:

val authorizationRequest = AuthorizationRequest.Builder()
    .setRequestedScopes(requestedScopes)
    .setAuthorizationType(AuthorizationRequest.AuthorizationType.CODE) // 切换为授权码模式,支持离线访问
    .setOfflineAccess(true) // 开启离线访问,触发refreshToken返回
    .build()

2. 用授权码交换包含RefreshToken的完整Token

切换到授权码模式后,authorizationResult不会直接返回accessToken,需要用授权码去交换完整的token信息(包含refreshToken)。修改授权成功后的处理逻辑:

Identity.getAuthorizationClient(this)
    .authorize(authorizationRequest)
    .addOnSuccessListener { authorizationResult ->
        if (authorizationResult.hasResolution()) {
            // 原有跳转授权UI的逻辑保持不变
        } else {
            // 提取授权码并发起token交换请求
            authorizationResult.authorizationCode?.let { code ->
                exchangeCodeForTokens(code)
            } ?: run {
                Log.e(LOG, "未获取到授权码")
            }
        }
    }

添加交换授权码的方法:

private fun exchangeCodeForTokens(code: String) {
    val tokenRequest = TokenRequest.newBuilder()
        .setClientId(YOUR_CLIENT_ID) // 替换为你的OAuth2客户端ID
        .setClientSecret(YOUR_CLIENT_SECRET) // 替换为你的OAuth2客户端密钥
        .setCode(code)
        .setGrantType("authorization_code")
        .setRedirectUri("urn:ietf:wg:oauth:2.0:oob") // Android应用默认回调URI
        .build()

    Identity.getAuthorizationClient(this)
        .getToken(tokenRequest)
        .addOnSuccessListener { tokenResponse ->
            val accessToken = AccessToken(tokenResponse.accessToken, Date(System.currentTimeMillis() + tokenResponse.expiresIn * 1000))
            val refreshToken = tokenResponse.refreshToken // 这里就能拿到refreshToken了
            // 构建支持自动刷新的UserCredentials
            val userCredentials = UserCredentials.newBuilder()
                .setClientId(YOUR_CLIENT_ID)
                .setClientSecret(YOUR_CLIENT_SECRET)
                .setAccessToken(accessToken)
                .setRefreshToken(refreshToken)
                .build()
            // 构建带自动刷新能力的Drive服务
            buildDriveServiceWithAutoRefresh(userCredentials)
        }
        .addOnFailureListener { e ->
            Log.e(LOG, "交换授权码失败: ${e.localizedMessage}")
        }
}

private fun buildDriveServiceWithAutoRefresh(credentials: UserCredentials) {
    driveService = Drive.Builder(
        GoogleNetHttpTransport.newTrustedTransport(),
        GsonFactory.getDefaultInstance(),
        HttpCredentialsAdapter(credentials)
    )
        .setApplicationName(app_name)
        .build()
}

3. 处理客户端密钥的安全风险

注意:在Android应用中直接硬编码客户端密钥存在泄露风险,建议通过后端服务代理token交换请求——前端将授权码发送到后端,由后端完成token交换后返回accessToken和refreshToken,避免客户端密钥暴露。

4. 解决重复获取相同AccessToken的问题

重启应用后调用authorize仍拿到旧token,是因为Credential Manager默认缓存有效凭证。如果需要强制刷新,可在构建请求时添加:

val authorizationRequest = AuthorizationRequest.Builder()
    // ...其他配置
    .setForceRefresh(true) // 强制刷新缓存的凭证
    .build()

5. 验证自动刷新逻辑

UserCredentials会在accessToken过期时自动用refreshToken获取新token,无需手动处理。可以模拟过期token测试:

// 模拟已过期的accessToken
val expiredToken = AccessToken("expired_token", Date(System.currentTimeMillis() - 3600 * 1000))
val userCredentials = UserCredentials.newBuilder()
    .setClientId(YOUR_CLIENT_ID)
    .setClientSecret(YOUR_CLIENT_SECRET)
    .setAccessToken(expiredToken)
    .setRefreshToken(validRefreshToken) // 有效的refreshToken
    .build()
// 调用Drive API时,会自动触发token刷新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 03:47:08