Google OAuth2 服务帐户访问令牌请求给出“无效请求"响应

时间:2023-03-29
本文介绍了Google OAuth2 服务帐户访问令牌请求给出“无效请求"响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

我正在尝试通过服务器到服务器方法与我的应用启用的 BigQuery API 进行通信.

I'm trying to communicate with my app's enabled BigQuery API via the server to server method.

我已勾选此 Google 指南上的所有方框,以构建我的 JWT尽我所能在 C# 中.

I've ticked all the boxes on this Google guide for constructing my JWT as best I can in C#.

我已经对所有必要的东西都进行了 Base64Url 编码.

And I've Base64Url encoded everything that was necessary.

但是,我从 google 得到的唯一响应是 400 Bad Request

However, the only response I get from google is a 400 Bad Request

"error" : "invalid_request"

我已经从这些其他 SO 问题中确定了以下所有内容:

I've made sure of all of the following from these other SO questions:

  • 签名正确加密使用 RSA 和 SHA256
  • 我正在使用 POST 并使用 application/x-www-form-urlencoded 内容类型
  • 转义了声明集中的所有反斜杠
  • 在 POST 数据中尝试了各种 grant_type 和断言值

当我使用 Fiddler 时,我得到了相同的结果.该错误消息令人沮丧地缺乏详细信息!我还能尝试什么?!这是我的代码:

I get the same result when I use Fiddler. The error message is frustratingly lacking in detail! What else can I try?! Here's my code:

class Program
{
    static void Main(string[] args)
    {
        // certificate
        var certificate = new X509Certificate2(@"<Path to my certificate>.p12", "notasecret");

        // header
        var header = new { typ = "JWT", alg = "RS256" };

        // claimset
        var times = GetExpiryAndIssueDate();
        var claimset = new
        {
            iss = "<email address of the client id of my app>",
            scope = "https://www.googleapis.com/auth/bigquery",
            aud = "https://accounts.google.com/o/oauth2/token",
            iat = times[0],
            exp = times[1],
        };

        // encoded header
        var headerSerialized = JsonConvert.SerializeObject(header);
        var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
        var headerEncoded = Base64UrlEncode(headerBytes);

        // encoded claimset
        var claimsetSerialized = JsonConvert.SerializeObject(claimset);
        var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
        var claimsetEncoded = Base64UrlEncode(claimsetBytes);

        // input
        var input = headerEncoded + "." + claimsetEncoded;
        var inputBytes = Encoding.UTF8.GetBytes(input);

        // signiture
        var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
        var cspParam = new CspParameters
        {
            KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
            KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
        };
        var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
        var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
        var signatureEncoded = Base64UrlEncode(signatureBytes);

        // jwt
        var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;

        Console.WriteLine(jwt);

        var client = new HttpClient();
        var uri = "https://accounts.google.com/o/oauth2/token";
        var post = new Dictionary<string, string>
        {
            {"assertion", jwt},
            {"grant_type", "urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer"}
        };
        var content = new FormUrlEncodedContent(post);
        var result = client.PostAsync(uri, content).Result;

        Console.WriteLine(result);
        Console.WriteLine(result.Content.ReadAsStringAsync().Result);
        Console.ReadLine();
    }

    private static int[] GetExpiryAndIssueDate()
    {
        var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        var issueTime = DateTime.Now;

        var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
        var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds;

        return new[]{iat, exp};
    }

    private static string Base64UrlEncode(byte[] input)
    {
        var output = Convert.ToBase64String(input);
        output = output.Split('=')[0]; // Remove any trailing '='s
        output = output.Replace('+', '-'); // 62nd char of encoding
        output = output.Replace('/', '_'); // 63rd char of encoding
        return output;
    }
}

推荐答案

看来我在上面评论中的猜测是正确的.我通过更改使您的代码正常工作:

Looks like my guess in the comment above was correct. I got your code working by changing:

"urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer"

到:

"urn:ietf:params:oauth:grant-type:jwt-bearer"

看起来你不小心对它进行了双重编码.

Looks like you were accidentally double-encoding it.

我现在收到的回复类似于:

I now get a response which looks something like:

{
  "access_token" : "1/_5pUwJZs9a545HSeXXXXXuNGITp1XtHhZXXxxyyaacqkbc",
  "token_type" : "Bearer",
  "expires_in" : 3600
}

编辑说明:请确保您的服务器上的日期/时间/时区/夏令时配置正确.将时钟延迟几秒钟也会导致 invalid_grant 错误.http://www.time.gov 将提供美国政府的官方时间,包括 UTC.

Edited Note: please make sure to have the correct date/time/timezone/dst configuration on your server. Having the clock off by even a few seconds will result in an invalid_grant error. http://www.time.gov will give the official time from the US govt, including in UTC.

这篇关于Google OAuth2 服务帐户访问令牌请求给出“无效请求"响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:验证 Google OpenID Connect JWT ID 令牌 下一篇:验证 AspNet.Security.OpenIdConnect.Server (ASP.NET vNext) 颁发的令

相关文章