刷新令牌时无法从请求错误中确定客户端 ID

Posted

技术标签:

【中文标题】刷新令牌时无法从请求错误中确定客户端 ID【英文标题】:Could not determine client ID from request error while refreshing token 【发布时间】:2021-08-03 16:42:52 【问题描述】:

我正在为我的 Google Oauth 设置使用已安装的应用程序客户端 ID。使用 GoogleWebAuthorizationBroker.AuthorizeAsync 成功进行用户身份验证后,我收到了来自 Google 的访问令牌,并且能够按预期使用 Google API。大约 1 小时后,如果我需要访问 Google API,我正在使用 [credential.RefreshTokenAsync] 函数来刷新访问令牌。即使使用刷新令牌,我也能够按预期访问 Google API。

但在随机情况下,我在刷新令牌时收到以下异常。我可以确认客户端 ID 没有问题,因为我的客户端 ID 是从永久文本文件中读取的。

更新:添加代码和更多细节。

由于 Google API 对我的应用场景的限制,我将应用设计如下:

    用户将从 Windows 窗体应用程序设置他们的 Google 帐户。 (“首次验证码”我在下面提到) 我将使用在第 1 步中从 Google 收到的令牌响应,在另一个应用程序中使用相同的客户端 ID/秘密(我在下面提到的“问题场景”代码)。因此,Google API 将查看令牌响应是否已经可用,如果可用,则不会显示身份验证页面并检查令牌是否已过期。如果过期,我将使用 RefreshTokenAsync 刷新令牌。

代码:

//First time authentication

 credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                                        new ClientSecrets
                                        
                                            ClientId,
                                            ClientSecret,
                                        ,
                                        scopes,
                                        GoogleAuthUser,
                                        CancellationToken.None, new FileDataStore(filepath, true)).Result;

                var oauthSerivce = new Google.Apis.Oauth2.v2.Oauth2Service(new BaseClientService.Initializer()
                
                    HttpClientInitializer = credential,
                    ApplicationName = "Appname",
                );

//Issue scenario:

credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                    new ClientSecrets
                    
                        ClientId,
                        ClientSecret,
                    ,
                    Scopes,
                    "user",
                    CancellationToken.None,
                    new FileDataStore(filepath, true)).Result;

            if (credential.Token.IsExpired(credential.Flow.Clock))
            
                if (credential.RefreshTokenAsync(CancellationToken.None).Result)
                
                
                else
                
                
            
var service = new GmailService(new BaseClientService.Initializer()
            
                HttpClientInitializer = credential,
                ApplicationName = ApplicationName,
            );

异常详情:

 (Inner Exception #0) Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"invalid_request", Description:"Could not determine client ID from request.", Uri:""
   at Google.Apis.Auth.OAuth2.Responses.TokenResponse.<FromHttpResponseAsync>d__36.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.<ExecuteAsync>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<RefreshTokenAsync>d__31.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Google.Apis.Auth.OAuth2.UserCredential.<RefreshTokenAsync>d__22.MoveNext()<---

【问题讨论】:

也许第 4 项:blog.timekit.io/… 为什么不让客户端库为您刷新它,这就是它的设计目的。请编辑您的问题并包含minimal reproducible example。在我看来,要么您的客户端 ID 不正确,要么您尝试使用使用不同客户端 ID 创建的刷新令牌。 @jdweng 没有错误消息,它实际上表示刷新令牌已过期。 @DaImTo 如果客户端 ID 或刷新令牌错误,则应该始终出现该问题。正如我之前提到的,问题在测试期间随机发生。另外,我在问题中添加了更多细节。请看一下。 【参考方案1】:

用户将通过 Windows 窗体应用设置他们的 Google 帐户。 (“首次验证码”我在下面提到)

实际上,除非您决定创建自己的 IDatastore 实现,否则您根本不会看到授权代码,自从您使用 fileDatastore 以来您似乎没有这样做。您没有在问题的任何地方提及授权码。

我将使用在第 1 步中从 Google 收到的令牌响应,并使用相同的客户端 ID/秘密(我在下面提到的“问题场景”代码)在不同的应用程序中。因此,Google API 将查看令牌响应是否已经可用,如果可用,则不会显示身份验证页面并检查令牌是否已过期。如果过期,我将使用 RefreshTokenAsync 刷新令牌。

实际上,GoogleWebAuthorizationBroker 通过检查您传递的用户表示的 fileDataStore 存储凭据的目录来处理所有这些。你的代码显然没有做你认为它在做的事情。

发布一个混合用户和混淆文件数据存储

我发现您的代码存在两个问题。首先是您正在混合用户。在以下代码中,您将存储名为 GoogleAuthUser 的用户的用户凭据。

credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                                        new ClientSecrets
                                        
                                            ClientId,
                                            ClientSecret,
                                        ,
                                        scopes,
                                        GoogleAuthUser,
                                        CancellationToken.None, new FileDataStore(filepath, true)).Result;

在以下代码中,您正在为名为 user 的用户存储/读取 redetinals

credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                    new ClientSecrets
                    
                        ClientId,
                        ClientSecret,
                    ,
                    Scopes,
                    "user",
                    CancellationToken.None,
                    new FileDataStore(filepath, true)).Result;

FileDataStore 将用户的凭据存储在您机器上的 %appdata% 中,并且用户由您发送的任何字符串表示。如果您在两个不同的调用中混合字符串,那么它将无法对用户进行身份验证。更多信息请咨询FileDatastore Demyistified

第二期已过期

客户端库旨在通过凭据对象处理授权,一旦将凭据对象加载到服务中,您就不必再担心它了,让库希望何时是刷新访问令牌的最佳时间

 if (credential.Token.IsExpired(credential.Flow.Clock))
            
                if (credential.RefreshTokenAsync(CancellationToken.None).Result)
                
                
                else
                
                
            

第三题

这到底是为了什么?

  var oauthSerivce = new Google.Apis.Oauth2.v2.Oauth2Service(new BaseClientService.Initializer()
                
                    HttpClientInitializer = credential,
                    ApplicationName = "Appname",
                );

【讨论】:

以上是关于刷新令牌时无法从请求错误中确定客户端 ID的主要内容,如果未能解决你的问题,请参考以下文章

何时将刷新令牌传递给 API

无法在 PHP 中刷新 OAuth2 令牌,授权无效

Apollo 客户端异步刷新令牌

云视界API的授权承载令牌。

Azure:如何获得刷新令牌?当连接的输出仅提供访问令牌时使用 Curl

尝试使用spring oauth2中的刷新令牌获取新的访问令牌时出现无效的客户端错误