android - 如何避免在 Google 登录对话框后获得错误的 token ?

标签 android token google-signin

背景
在应用程序内部,有一个使用 token 向服务器注册的 Google 登录步骤。这是通过以下依赖项完成的:

api('com.google.android.gms:play-services-auth:20.0.0')
该应用程序触发器最多为用户显示 2 个对话框:
  • 登录(如果用户已登录当前安装的应用程序,则不显示):

  • ![enter image description here
  • 授予权限(如果过去授予权限,则不显示):

  • enter image description here
    这适用于大多数情况。
    如果用户已经登录并授予权限,我们可以使用上次获得的 token ,假设它没有过期。我检查它是否已过期使用:
    GoogleSignIn.getLastSignedInAccount(context)?.isExpired`) .
    
    问题
    我发现了一个特殊的问题场景:
  • 用户过去曾登录并授予了一些权限
  • 用户前往 Google-account-manager 并撤销了对应用程序的访问权限 (here)。
  • 用户尝试再次登录(例如在删除应用程序后)

  • 在这种情况下,我得到了一个无法使用的坏 token 。这实际上与我在撤销访问权限之前获得的 token 完全相同。它可能使用了上次缓存的 token ,以避免与 Google 服务器进行不必要的通信。
    在这种情况下,服务器(我使用的 SDK 的)将在尝试使用它时向我发送此 token 无效(这是正确的)的错误。
    这是有问题的,在我看来就像谷歌 SDK 上的一个错误(我已经报告了 here),因为 token 应该可以工作,因为用户已经使用登录对话框重新登录,因为一切都已重置。
    我试过的
    我尝试使用各种 API 函数,但它们似乎都没有告诉我 token 是否有效,或者让我请求一个新的 token 用于登录对话框,以防当前 token 无效。
    我发现的唯一解决方法是,在一次登录对话框后,检测到 token 有错误(通过服务器获取),我选择完全注销并重新登录:
    @WorkerThread
    fun logout(context: Context, googleClientId: String) {
        val options =
            GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestServerAuthCode(googleClientId)
                .requestEmail()
                .build()
        val signInClient = GoogleSignIn.getClient(context, options)
        val lastSignedInAccount = GoogleSignIn.getLastSignedInAccount(context)
        if (lastSignedInAccount != null) {
            Tasks.await(signInClient.revokeAccess())
            Tasks.await(signInClient.signOut())
        }
    }
    
    只有在那之后,我才能使用我准备的 Intent 登录:
    @WorkerThread
    fun prepareIntent(context: Context, googleClientId: String): Intent {
        val options =
            GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestServerAuthCode(googleClientId)
                .requestEmail()
                .build()
        val signInClient = GoogleSignIn.getClient(context, options)
        val lastSignedInAccount = GoogleSignIn.getLastSignedInAccount(context)
        if (lastSignedInAccount?.isExpired == true) {
            var success = false
            kotlin.runCatching {
                val result: GoogleSignInAccount? = Tasks.await(signInClient.silentSignIn())
                success = result?.isExpired == false
            }
            if (!success)
                kotlin.runCatching {
                    Tasks.await(signInClient.revokeAccess())
                    Tasks.await(signInClient.signOut())
                }
        }
        return signInClient.signInIntent
    }
    
    这不是一件好事,因为用户会看到 3 个对话框,而不是我在开头显示的最多 2 个对话框:
  • 登录
  • 授予权限
  • 再次登录,因为 token 无效。

  • 问题
  • 如何避免 2 个登录对话框?
  • 是否有强制获取登录对话框的新 token 的 API?
  • 这是一个已知的错误,这是我确实可以使用的唯一解决方法吗?
  • 最佳答案

    这不是错误 - 这种行为是故意的,您必须正确处理它。
    据google官方user consent policy如果您的用户已撤销对某些功能的访问权限,您将不得不再次询问他们。
    就您的应用程序的 UI/UX 而言,这意味着什么?这得看情况。如果您需要访问的功能位于一些单独的很少可访问的页面上 - 您可以强制用户仅在进入这些页面时重新登录。如果您的整个应用程序依赖于这些功能 - 您将不得不强制重新登录用户进入您的应用程序。
    要理解的重要一点是,无论如何,您都必须重新登录用户并再次征求他们的同意 - 没有其他办法。如果同意被撤销,您将无法静默获取新 token 。这是一个有意的限制。
    关于两个对话框 - 如果某些范围是双方同意的,您将无法避免它。
    关于实际的实现,有几种方式。

  • 最简单的方法是等待具有无效 token 的请求和错误重新登录用户的访问错误。
  • 更难但对用户/开发人员更友好的方法是通过 https://www.googleapis.com/oauth2/v2/tokeninfo?access_token=YOUR_TOKEN 检查 token 的授权范围(我不确定 SDK 是否有这个功能,也有这个请求的 v1 和 v3 版本。)这会给你类似的响应{ "audience":"", "user_id":"", "scope":"https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", "expires_in":0 }scope字段是当前 token 范围的空格分隔列表。如果当前功能不需要范围 - 只需使用所需范围重新登录用户即可。

  • 当然,该方法仅适用于通用权限的范围访问权限(如背景位置披露或特定分析收集),您将不得不等待错误。

    关于android - 如何避免在 Google 登录对话框后获得错误的 token ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70377774/

    相关文章:

    android - 将文件 uri 转换为内容 uri

    java - 如何在android项目中使用java Lucence库?

    Android webview twitter timeline widget feed 不更新

    c# - 当我通过安装程序运行应用程序时,出现错误 "Bad method token. "

    flutter - 在 Flutter 中使用 Google 登录的错误 403 受限客户端

    android - 谷歌登录不显示具有单个谷歌帐户的设备的帐户选择器

    android - findViewById() 什么时候返回 null?

    java - 在类之间发送 token

    oauth - 为什么 OAuth 设计为具有请求 token 和访问 token ?

    amazon-web-services - AWS Cognito : Best practice to handle same user (with same email address) signing in from different identity providers (Google, Facebook)