C# Google OAuth2服务帐户访问令牌请求提供';无效请求';回应

C# Google OAuth2服务帐户访问令牌请求提供';无效请求';回应,c#,oauth-2.0,sha256,google-bigquery,jwt,C#,Oauth 2.0,Sha256,Google Bigquery,Jwt,我正在尝试通过服务器到服务器的方法与我的应用程序启用的BigQuery API通信 我已经勾选了所有的方框,以便在C#中尽我所能构建我的JWT 我已经对所有必要的东西进行了编码 然而,我从谷歌得到的唯一回应是400个错误的请求 "error" : "invalid_request" 我已经从这些其他SO问题中确定了以下所有内容: 当我使用小提琴时,我得到了同样的结果。令人沮丧的是,错误消息缺少细节!我还能试什么?!这是我的密码: class Program { static

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

我已经勾选了所有的方框,以便在C#中尽我所能构建我的JWT

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

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

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

当我使用小提琴时,我得到了同样的结果。令人沮丧的是,错误消息缺少细节!我还能试什么?!这是我的密码:

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;
    }
}
类程序
{
静态void Main(字符串[]参数)
{
//证书
var证书=新的X509Certificate2(@.p12,“notasecret”);
//标题
var header=new{typ=“JWT”,alg=“RS256”};
//索赔集
var times=GetExpiryAndIssueDate();
var claimset=new
{
iss=“”,
范围=”https://www.googleapis.com/auth/bigquery",
澳元=”https://accounts.google.com/o/oauth2/token",
iat=次[0],
exp=次[1],
};
//编码头
var headerSerialized=JsonConvert.Serialized对象(标头);
var headerBytes=Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded=Base64UrlEncode(headerBytes);
//编码索赔集
var claimsetSerialized=JsonConvert.serialized对象(claimset);
var claimsetBytes=Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded=Base64UrlEncode(claimsetBytes);
//输入
var输入=headerEncoded+“”+claimsetEncoded;
var inputBytes=Encoding.UTF8.GetBytes(输入);
//符号
var rsa=certificate.PrivateKey作为RSACryptoServiceProvider;
var csparam=新的CSP参数
{
KeyContainerName=rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber=rsa.CspKeyContainerInfo.KeyNumber==KeyNumber.Exchange?1:2
};
var aescsp=new RSACryptoServiceProvider(csparam){PersistKeyInCsp=false};
var signatureBytes=aescsp.SignData(输入字节,“SHA256”);
var signatureEncoded=Base64UrlEncode(signatureBytes);
//jwt
var jwt=headerEncoded+“+”claimsetEncoded+“+”signatureEncoded;
控制台写入线(jwt);
var client=新的HttpClient();
var uri=”https://accounts.google.com/o/oauth2/token";
var post=新字典
{
{“断言”,jwt},
{“授权类型”,“urn%3Aietf%3Aparams%3Aoauth%3Agrant类型%3Ajwt承载者”}
};
var内容=新的FormUrlEncodedContent(post);
var result=client.PostAsync(uri,content.result);
控制台写入线(结果);
Console.WriteLine(result.Content.ReadAsStringAsync().result);
Console.ReadLine();
}
私有静态int[]GetExpiryAndIssueDate()
{
var utc0=新的日期时间(1970年,1年,1年,0年,0年,0年,DateTimeKind.Utc);
var issueTime=DateTime.Now;
var iat=(int)issueTime.Subtract(utc0).TotalSeconds;
var exp=(int)issueTime.AddMinutes(55).减法(utc0).TotalSeconds;
返回新的[]{iat,exp};
}
专用静态字符串Base64UrlEncode(字节[]输入)
{
var输出=Convert.tobase64字符串(输入);
output=output.Split('=')[0];//删除任何尾随'='
output=output.Replace('+','-');//编码的第62个字符
output=output.Replace('/',''');//编码的第63个字符
返回输出;
}
}

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

“urn%3Aietf%3Aparams%3Aoauth%3Agrant类型%3Ajwt承载”

致:

“urn:ietf:params:oauth:grant-type:jwt-bearer”

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

我现在得到一个类似以下内容的响应:

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

编辑说明:请确保服务器上的日期/时间/时区/dst配置正确。即使时钟关闭几秒钟,也会导致
无效授权
错误。将给出美国政府的官方时间,包括UTC。

请确保使用DateTime.UtcNow而不是DateTime。现在使用GetExpiryAndIssueDate方法。

我没有发现任何明显的问题,我已经浏览了您的每一行代码。有一件事可能是,您正在字典中对grant类型进行编码,而formurlencodedecontent可能最终会对其进行双重编码。所以,我会尝试“urn:ietf:params:oauth:grant type:jwt bearer”,看起来HttpClient来自最近的.NET框架版本,所以我正在安装它,并直接测试代码。我还从内部接触了一些可能会提供帮助的人。