将 ID 令牌交换为访问令牌时,Google 身份验证代码流请求令牌生成无效的客户端错误

时间:2021-04-18 16:14:03

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

刚开始了解 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 details

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

有两个查询。

  1. 我不确定“UserId”应该是什么,很多帖子都只包含“userid”或“me”,那么它是做什么用的?
  2. 既然 Flow 类是通过相同的设置构建的,为什么客户端会突然失效。

提前致谢。

谷歌配置

Google Web Client created

目的是能够阅读来自此帐户的电子邮件(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()

我已经包含了这段代码,因为它看起来确实调用了删除,并且在返回 Task.CompletedTask 时出现错误(在初始调用时,商店为空,所以只返回 Task.CompletedTask

1 个答案:

答案 0 :(得分:0)

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

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

评论更新

如果你查看文档 MVC

检查 AppFlowMetadata 部分

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

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

它使用该会话变量来知道从哪些用户数据加载

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

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