【问题标题】:How to migrate from GoogleCredential to GoogleCredentials and still get access to People API?如何从 GoogleCredential 迁移到 GoogleCredentials 并且仍然可以访问 People API?
【发布时间】:2021-08-02 15:07:31
【问题描述】:

背景

对于我正在开发的应用程序,它使用 People API 并使用凭据(用户登录)。用户提供凭据后,我可以访问各种 Google API,例如 People API。一个示例是获取联系人列表:

https://developers.google.com/people/api/rest/v1/people.connections/list

我注意到 com.google.api.client.googleapis.auth.oauth2.GoogleCredential 类已被弃用:

https://googleapis.dev/java/google-api-client/latest/com/google/api/client/googleapis/auth/oauth2/GoogleCredential.html

问题

该应用的旧代码基于一些旧的 G+ 代码 (here),用于通过 Google 帐户联系联系人。这是其中最重要部分的 sn-p,这让我很难从它迁移出去:

object GoogleOuthHelper {
    @WorkerThread
    fun setUp(context: Context, serverAuthCode: String?): Services {
        val httpTransport: HttpTransport = NetHttpTransport()
        val jsonFactory = JacksonFactory.getDefaultInstance()
        // Redirect URL for web based applications. Can be empty too.
        val redirectUrl = "urn:ietf:wg:oauth:2.0:oob"
        // Exchange auth code for access token
        val tokenResponse = GoogleAuthorizationCodeTokenRequest(
            httpTransport, jsonFactory, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
            serverAuthCode, redirectUrl)
            .execute()
        // Then, create a GoogleCredential object using the tokens from GoogleTokenResponse
        val credential = GoogleCredential.Builder()
            .setClientSecrets(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
            .setTransport(httpTransport)
            .setJsonFactory(jsonFactory)
            .build()
        val accessToken = tokenResponse.accessToken
        getDefaultSecuredSharedPreferences(context).edit()
            .putString(SecuredSharedPreferences.KEY__GOOGLE_ACCESS_TOKEN, accessToken).apply()
        credential.setFromTokenResponse(tokenResponse)
        val appPackageName = context.packageName
        val peopleServiceApi = PeopleService.Builder(httpTransport, jsonFactory, credential)
            .setApplicationName(appPackageName)
            .build()
        val peopleService = peopleServiceApi.people()
        val otherContactsService = peopleServiceApi.otherContacts()
        val contactGroups = peopleServiceApi.contactGroups()
        return Services(peopleService, otherContactsService, contactGroups)
    }

    class Services(
        /**https://developers.google.com/people/api/rest/v1/people*/
        val peopleService: PeopleService.People,
        /**https://developers.google.com/people/api/rest/v1/otherContacts*/
        val otherContactsService: OtherContacts,
        /**https://developers.google.com/people/api/rest/v1/contactGroups*/
        val contactGroups: ContactGroups)
}

问题甚至从一开始就存在:

GoogleCredentials 类似乎不接受我为 GoogleCredential 类提供的任何东西。

要添加更多内容,此函数将“serverAuthCode”作为参数,它来自GoogleSignInAccount,但要获取它,我需要使用已弃用的GoogleApiClient类:

        fun prepareGoogleApiClient(someContext: Context): GoogleApiClient {
            val context = someContext.applicationContext ?: someContext
            val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestServerAuthCode(GOOGLE_CLIENT_ID)
                .requestEmail()
                .requestScopes(
                    Scope(PeopleServiceScopes.CONTACTS_READONLY),
                    Scope(PeopleServiceScopes.USERINFO_PROFILE),
                    Scope(PeopleServiceScopes.USER_EMAILS_READ),
                    Scope(PeopleServiceScopes.CONTACTS),
                    Scope(PeopleServiceScopes.CONTACTS_OTHER_READONLY)
                )
                .build()
            return GoogleApiClient.Builder(context)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build()
        }

这就是我用它做的:

val connectionResult = googleApiClient!!.blockingConnect()
if (!connectionResult.isSuccess)
    return
val operation = Auth.GoogleSignInApi.silentSignIn(googleApiClient)
val googleSignInResult: GoogleSignInResult = operation.await()
val googleSignInAccount = googleSignInResult.signInAccount
//use googleSignInAccount.serverAuthCode in setUp() function above

Gradle 文件具有以下依赖项:

// https://mvnrepository.com/artifact/com.google.auth/google-auth-library-oauth2-http
implementation 'com.google.auth:google-auth-library-oauth2-http:0.26.0'
// https://github.com/googleapis/google-api-java-client-services/tree/master/clients/google-api-services-people/v1#gradle   https://mvnrepository.com/artifact/com.google.apis/google-api-services-people
implementation 'com.google.apis:google-api-services-people:v1-rev20210515-1.31.0'

我尝试过的

除了查看文档(并且没有看到与我必须处理的相似之处)之外,我尝试在这里写下这个。

遗憾的是,我还没有找到摆脱旧代码的方法。

我试图在那里询问如何迁移(herehere),但还没有得到答案。

问题

如何在仍使用 People API 等各种 API 的同时从 GoogleCredential 迁移到 GoogleCredentials

换句话说:如何避免在 Android 上使用这些已弃用的类(GoogleCredential 和 GoogleApiClient),同时仍然能够使用各种 API?

【问题讨论】:

  • 我发现有关 Google 凭据的文档相当不透明。我试图了解您在寻找什么。您是否尝试在不让用户再次登录的情况下迁移到 GoogleCredentials?或者您是否正在寻找如何将 GoogleCredentials 与各种 API 一起使用的示例?还是别的什么?
  • 我编写的代码用于访问 Google People API,例如获取 Google 联系人中的联系人列表:developers.google.com/people/api/rest/v1/people.connections/…。它需要登录,这是有道理的。我正在尝试找到如何从已弃用的代码(我在这里至少展示了 2 个已弃用的地方)迁移到新的代码。我会更新问题。
  • 你能发布应用程序的 build.gradle 文件吗?我有兴趣知道您从哪里拉 PeopleService.Builder()?我问是因为我认为争论可能发生了变化,但我不确定。我只是想证实我的怀疑。
  • 也许它与旧的 G+ 代码相关联,但您是否还需要使用 GoogleCredentials?查看 REST API 示例的 Google signin quickstart 特别是 this line。在那里,GoogleAccountCredential 用于凭据,这是我一直在使用的。也许我错过了什么。
  • @Cheticamp 更新问题以保存相关依赖项。旧的 G+ 代码只是一切的起点。它随着时间而改变。我现在已经将代码更新为它所更改的内容。我不明白如何使用你发给我的东西。你试过了吗?你说登录程序本身也要改?我看到它有Account 作为参数,但我有的是GoogleSignInAccount(不扩展它)。

标签: java android oauth-2.0 google-api google-people-api


【解决方案1】:

我如何才能从 GoogleCredential 迁移到 GoogleCredentials,同时仍然使用各种 API,例如 People API?

换句话说:如何避免在 Android 上使用这些已弃用的类(GoogleCredential 和 GoogleApiClient),同时仍然能够使用各种 API?

虽然您可以使 GoogleCredentials 直接工作,但最好使用从 GoogleCredentials 派生的类,例如 UserCredentials 来容纳令牌像 GoogleCredential 一样刷新。 GoogleCredentials 更像是一个基础类。

以下代码使用 UserCredentials。这主要是您介绍的内容,但为了演示的目的,我更改了一些凭证存储逻辑。除了startActivityForResult(),这段代码没有弃用的方法。

serverAuthCode 可从 GoogleSignInAccount 获得。查看Moving Past GoogleApiClient,了解如何删除对GoogleApiClient 的依赖。我已经从 Google signin quickstart 更新了我的 RestApiActivitypublic gist,它展示了如何使用 GoogleOauthHelper 以及 GoogleApi

GoogleOauthHelper.kt

object GoogleOauthHelper {
    @WorkerThread
    fun setUp(context: Context, serverAuthCode: String?): Services {
        val httpTransport: HttpTransport = NetHttpTransport()
        val jsonFactory = GsonFactory.getDefaultInstance()
        // Redirect URL for web based applications. Can be empty too.
        val redirectUrl = "urn:ietf:wg:oauth:2.0:oob"

        // Patch for demo
        val GOOGLE_CLIENT_ID = context.getString(R.string.GOOGLE_CLIENT_ID)
        val GOOGLE_CLIENT_SECRET = context.getString(R.string.GOOGLE_CLIENT_SECRET)

        var accessToken: AccessToken? = null
        var refreshToken =
            getDefaultSecuredSharedPreferences(context).getString(
                SecuredSharedPreferences.KEY_GOOGLE_REFRESH_TOKEN,
                null
            )

        if (refreshToken == null) {
            /*  Did we lose the refresh token, or is this the first time? Refresh tokens are only
                returned the first time after the user grants us permission to use the API. So, if
                this is the first time doing this, we should get a refresh token. If it's not the
                first time, we will not get a refresh token, so we will proceed with the access
                token alone. If the access token expires (in about an hour), we will get an error.
                What we should do is to ask the user to reauthorize the app and go through the
                OAuth flow again to recover a refresh token.

                See https://developers.google.com/identity/protocols/oauth2#expiration regarding
                how a refresh token can become invalid.
            */
            val tokenResponse = GoogleAuthorizationCodeTokenRequest(
                httpTransport, jsonFactory, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
                serverAuthCode, redirectUrl
            ).execute()

            refreshToken = tokenResponse.refreshToken
            if (refreshToken != null) {
                getDefaultSecuredSharedPreferences(context).edit()
                    .putString(SecuredSharedPreferences.KEY_GOOGLE_REFRESH_TOKEN, refreshToken)
                    .apply()
            } else {
                Log.d("Applog", "No refresh token. Going with access token alone.")
                val expiresAtMilliseconds =
                    Clock.SYSTEM.currentTimeMillis() + tokenResponse.expiresInSeconds * 1000
                accessToken = AccessToken(tokenResponse.accessToken, Date(expiresAtMilliseconds))
            }
        }

        Log.d("Applog", "Refresh token: $refreshToken")
        // UserCredentials extends GoogleCredentials and permits token refreshing.
        val googleCredentials = UserCredentials.newBuilder().run {
            clientId = GOOGLE_CLIENT_ID
            clientSecret = GOOGLE_CLIENT_SECRET
            setRefreshToken(refreshToken)
            setAccessToken(accessToken)
            build()
        }

        // Save access token on change
        googleCredentials.addChangeListener { oAuth2Credentials ->
            saveAccessToken(oAuth2Credentials.accessToken)
        }

        val requestInitializer: HttpRequestInitializer = HttpCredentialsAdapter(googleCredentials)
        val appPackageName = context.packageName

        val peopleServiceApi = PeopleService.Builder(httpTransport, jsonFactory, requestInitializer)
            .setApplicationName(appPackageName)
            .build()

        return peopleServiceApi.run { Services(people(), otherContacts(), contactGroups()) }
    }

    private fun saveAccessToken(accessToken: AccessToken) {
        // Persist the token securely.
        Log.d("Applog", "Access token has changed: ${accessToken.tokenValue}")
    }

    // Warning insecure!: Patch for demo.
    private fun getDefaultSecuredSharedPreferences(context: Context): SharedPreferences {
        return PreferenceManager.getDefaultSharedPreferences(context)
    }

    // Warning insecure!: Patch for demo.
    object SecuredSharedPreferences {
        const val KEY_GOOGLE_REFRESH_TOKEN = "GOOGLE_REFRESH_TOKEN"
    }

    class Services(
        /**https://developers.google.com/people/api/rest/v1/people*/
        val peopleService: PeopleService.People,
        /**https://developers.google.com/people/api/rest/v1/otherContacts*/
        val otherContactsService: PeopleService.OtherContacts,
        /**https://developers.google.com/people/api/rest/v1/contactGroups*/
        val contactGroups: PeopleService.ContactGroups,
    )
}

我在 GitHub 上发布了demo project

【讨论】:

  • 太棒了!很不错!关于saveAccessToken,它不应该类似于之前使用 KEY__GOOGLE_ACCESS_TOKEN 保存的值,只是该值将是 `oAuth2Credentials.accessToken.tokenValue` 吗?至于登录,使用新的GoogleSignInClient,我可以看到它缺少我以前的各种功能:blockingConnect(现在似乎不存在),isConnected(相同),clearDefaultAccountAndReconnectdisconnect(也许只需要googleApiClient.signOut()?但我该如何开始呢?)。我希望我已经找到了替代品。你能把你的样品也放在这里吗?
  • 我添加了一个存根方法来展示如何捕获访问令牌,以防由于某种原因需要保存它。原始代码中的相同安全存储逻辑应该可以按您所说的那样工作。关于blockingConnect() 调用,这是针对GoogleApiClient 的。对于新的登录方式,我没有看到任何类似的东西。要退出,请参阅 RestApiActivity.java 中的 signOut 方法。我还发布了一个我在答案中提到的演示项目。
  • 不错。谢谢你。不过,在这里也能看到它也很高兴。
  • 我也找到了这个教程,不知道你有没有提到:developers.google.com/people/quickstart/java
  • @androiddeveloper 我看到了该教程,但决定改用登录快速入门。不过,没有什么反对该教程的。我还要提到 UserCredentials 有一个保存功能,如果你给它一个安全的地方来存储文件,它可以为凭据创建一个 JSON 文件。然后,您可以从文件中加载凭据。
猜你喜欢
  • 2022-11-08
  • 2018-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多