当前位置: 首页 > 知识库问答 >
问题:

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

徐涵亮
2023-03-14

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

我已经勾选了谷歌指南上的所有方框,以尽可能用C#构建我的JWT。

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

然而,我从谷歌得到的唯一回复是400错误请求

"error" : "invalid_request"

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

  • 使用 RSA 和 SHA256 正确加密签名
  • 我正在使用 POST 并使用应用程序/x-www-表单-网址编码的内容类型
  • 转义了声明集中的所有反斜杠
  • 已尝试POST数据中的各种grant_type和断言值

当我使用小提琴手时,我得到相同的结果。令人沮丧的是,错误消息缺乏细节!我还能尝试什么?!这是我的代码

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;
    }
}

共有2个答案

赵骏奇
2023-03-14

请务必使用DateTime.UtcNow而不是在GetExiryAndIssueDate方法中DateTime.Now。

阴高刚
2023-03-14

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

< code > " urn:IETF:params:oauth:grant-type:jwt-bearer "

收件人:

< code > " urn:IETF:params:oauth:grant-type:jwt-bearer "

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

我现在得到了一个类似这样的响应:

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

编辑说明:请确保您的服务器上有正确的日期/时间/时区/dst配置。让时钟慢几秒钟都会导致< code>invalid_grant错误。http://www.time.gov将给出美国政府的官方时间,包括协调世界时。

 类似资料:
  • 我已经成功地使用Google API(通过HTTP/REST,以及使用.NET客户端库)和Google服务帐户访问Google Drive中的文件。 最近,我正在探索融合表。我能够通过web应用程序使用具有用户授权的API。然而,当我尝试在同一个项目下使用Google服务帐户访问它时,它失败了,每当我https://www.googleapis.com/auth/fusiontables范围内:

  • 客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求: grant_type 必需的。值必须设置为“client_credentials”。 scope 可选的。如3.3节所述的访问请求的范围。 客户端必须如3.2.1所述与授权服务器进行身份验证。 例如,客户端使用传输层安全发起如

  • 客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求: grant_type 必需的。值必须设置为“password”。 username 必需的。资源所有者的用户名。 password 必需的。资源所有者的密码。 scope 可选的。如3.3节所述的访问请求的范围。 如果客户端类

  • 客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求: grant_type 必需的。值必须被设置为“authorization_code”。 code 从授权服务器收到的授权码。 redirect_uri 必需的,若“redirect_uri”参数如4.1.1节所述包含在授权请求

  • 我遵循以下文件:https://account-d.docusign.com/oauth/token 我正在为回调API尝试以下操作: 等 上面是url调用的回调函数,看起来像这样:https://account-d.docusign.com/oauth/auth?response_type=code 然而,我得到了“坏请求”,请求。查询代码是正确的,我不确定组合。 我错过了什么?

  • 概述 我将使用API网关作为基于Spring Security性的身份验证。我刚刚按照https://spring.io/guides/tutorials/spring-security-and-angular-js/链接中的步骤创建了一个基于其对应的github项目的“对双”模块的项目https://github.com/spring-guides/tut-spring-security-and