【问题标题】:Google Auth Code Flow requesting token generates invalid client error when exchanging ID token for Access Token将 ID 令牌交换为访问令牌时,Google 身份验证代码流请求令牌会生成无效的客户端错误
【发布时间】:2021-07-12 23:38:51
【问题描述】:

在查看 Google OAuth 库方面相当新,但在 Azure/Exchange OAuth 中花了一段时间。 我们对适配器模式中的各种库进行了包装,因此我们的应用程序代码是一致的并调用相同的方法。这是 Google 的样子

private string _state;
private GoogleAuthorizationCodeFlow _flow;

public GoogleTokenProvider(IOAuthSettings settings, string state, string baseUrl) : base(settings, state, baseUrl)
{
    //https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-asp.net-mvc
    _state = state;
    GoogleAuthorizationCodeFlow.Initializer init = new GoogleAuthorizationCodeFlow.Initializer {
        ClientSecrets = new ClientSecrets
        {
            ClientId = settings.ClientId,
            ClientSecret = settings.ClientSecret
        },
        Scopes = this.Scopes,
        DataStore = new GoogleTokenStore(settings) //Our implementation - not called yet
    };
    _flow = new GoogleAuthorizationCodeFlow(init);
}

第一步(一旦构建)是获取启动认证过程的url。

public string GetAuthorizationUrl()
{
    //Using Flow 
    var req = _flow.CreateAuthorizationCodeRequest(this.RedirectUrl);
    var baseUrl = req.Build().AbsoluteUri;
    baseUrl += "&state=" + this._state;
    return baseUrl;

    // Manually build request
    //StringBuilder builder = new StringBuilder();
    //builder.Append("https://accounts.google.com/o/oauth2/v2/auth?");
    //builder.AppendFormat("scope={0}", String.Join("+", this.Scopes));
    //builder.Append("&access_type=offline&include_granted_scopes=true&response_type=code");
    //builder.AppendFormat("&state={0}&", this._state);
    //builder.AppendFormat("&redirect_uri={0}", this.RedirectUrl);
    //builder.AppendFormat("&client_id={0}", this.settings.ClientId);
    //return builder.ToString();
}

这行得通,我们被引导到 OAuth 站点,我们使用我们的 Google 帐户登录。我们的回调被触发,在调试回调的时候,我们得到了代码,传递的状态,并调用了这个方法:

public string AcquireTokenByAuthorizationCode(string code)
{
    Google.Apis.Auth.OAuth2.Responses.TokenResponse result = _flow.ExchangeCodeForTokenAsync("userid", code, RedirectUrl, new System.Threading.CancellationToken()).Result;
    return result.AccessToken;
}

但是,这会导致错误:

Error:"invalid_client", Description:"Unauthorized", Uri:""

有两个查询。

  1. 我不确定“UserId”应该是什么,很多帖子里面只有“userid”或“me”,那它是干什么用的?
  2. 既然Flow类都是用同样的设置构建的,为什么客户端突然失效了。

提前致谢。

谷歌配置

目的是能够阅读来自该帐户的电子邮件(Gmail)

启用的 API:

  • Gmail API
  • Google+ API

谷歌数据存储实现

public class GoogleTokenStore : IDataStore
{
    private readonly IOAuthSettings _settings;
    public GoogleTokenStore(IOAuthSettings settings)
    {
        this._settings = settings;
    }
    private Dictionary<string, T> Decode<T>()
    {
        using (var memoryStream = new MemoryStream())
        {
            var binaryFormatter = new BinaryFormatter();
            
            memoryStream.Write(_settings.TokenStore, 0, _settings.TokenStore.Length);
            memoryStream.Seek(0, SeekOrigin.Begin);
            return binaryFormatter.Deserialize(memoryStream) as Dictionary<string, T>;
        }
    }

    private void Encode<T>(Dictionary<string, T> store)
    {
        using (var memoryStream = new MemoryStream())
        {
            var binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, store);
            _settings.TokenStore = memoryStream.ToArray();
        }
    }

    public Task ClearAsync()
    {
        _settings.TokenStore = null;
        return Task.CompletedTask;
    }

    public Task DeleteAsync<T>(string key)
    {
        if (_settings.TokenStore == null)
        {
            return Task.CompletedTask; //THIS IS CALLED, RETURNS HERE
        }
        var store = Decode<T>();
        if (store.ContainsKey(key))
        {
            store.Remove(key);
            Encode<T>(store);
        }
        return Task.CompletedTask;
    }

    public Task<T> GetAsync<T>(string key)
    {
        var store = Decode<T>();
        if (store.ContainsKey(key))
        {
            return Task.FromResult( store[key] );
        }
        return null;
    }

    public Task StoreAsync<T>(string key, T value)
    {
        var store = Decode<T>();
        if(store != null)
        {
            store.Add(key, value);
            Encode<T>(store);
        }
        return Task.CompletedTask;
    }
}

Google 错误的堆栈跟踪

   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.<ExchangeCodeForTokenAsync>d__30.MoveNext()

我已包含此代码,因为它确实显示了 Delete 被调用,并且在返回 Task.CompletedTask 时出现错误(在初始调用时,商店为空,因此只需返回 Task.CompletedTask

【问题讨论】:

    标签: c# oauth-2.0 google-api google-oauth google-api-dotnet-client


    【解决方案1】:

    Invalid_client 错误通常表示您在 Google Developer Console 上创建了一种类型的客户端,并且正在将代码用于不同类型的客户端。

    .net 应用程序桌面应用程序和网络浏览器客户端通常使用两种类型的客户端。用于它们的代码是不同的。您似乎正在使用 MCV 代码。我会仔细检查您是否已在 Google 开发者控制台上创建了 Web 浏览器客户端。

    从 cmets 更新

    如果您查看文档 MVC

    检查 AppFlowMetadata 部分

    你会遇到问题,因为它在每个用户浏览器上寻找会话变量

    var user = controller.Session["user"];
    

    它使用该会话变量来了解要从哪些用户数据加载

    DataStore = new FileDataStore("Drive.Api.Auth.Store")
    

    因此,如果您想让这个单一用户确保始终将会话变量设置为您要加载其数据的用户的变量。检查 %appData% 文件数据存储默认存储每个用户的凭据文件的位置。 Check this if your curios what IDataStore is

    【讨论】:

    • 您好 DalmTo - 我用 Google 设置的一些信息更新了这个问题,如果有更多帮助,请告诉我。它似乎设置为 Web 客户端。目的是使用此令牌检查帐户上的电子邮件并进行处理,因此这是一次身份验证,然后在此之后重新使用令牌/刷新。
    • 我不知道 MVC 可以让你以这种方式向它传递一个刷新令牌。您是否还可以在存储刷新令牌的位置发布代码以供以后使用,我假设它存储在会话 cookie 中。如果它是一次性授权,从技术上讲,您可以只使用已安装的应用程序代码,然后将刷新令牌与您的项目一起作为 env var 或其他东西上传。
    • 嗯,那很可能是我的下一个问题!也许我走错了路。我还没有编写代码来进行刷新,我正在进行初始身份验证。我得到了同意屏幕,跳过了所有的 Google 箍,然后点击了 redirectUrl 回调。我提供了一个代码,然后我尝试将该代码交换为访问令牌并得到错误。有更好的流程/流程吗?
    • 问题是客户端库旨在为您处理所有刷新。你不应该这样做,你应该让图书馆处理它。问题是每个想要查看该页面的用户都需要跳过所有这些授权圈。授权是基于用户的,每个用户都需要被授权。
    • 只有系统管理员才能看到它,他们将验证电子邮件箱,然后应用程序将每 5-10 分钟读取一次。我确实开始手动创建请求(注释掉的代码),但转而使用客户端库。该库创建请求 url,我们去那里,我们进行身份验证,我们返回,它正在将代码交换为一个出错的令牌。我希望库在请求令牌时会自动刷新等,只是还没有那么远。
    猜你喜欢
    • 2017-11-22
    • 2018-10-22
    • 2011-10-25
    • 2012-06-29
    • 1970-01-01
    • 2021-12-10
    • 2013-08-17
    • 1970-01-01
    • 2014-05-12
    相关资源
    最近更新 更多