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

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

我已经在这个谷歌指南上勾选了我在J#中尽我所能构建JWT的所有方框。

我对Base64Url编码了所有必要的东西。

但是,我从谷歌获得的唯一回应是400 Bad Request

"error" : "invalid_request" 

我从其他SO问题中确认了以下所有问题:

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

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

 class Program { static void Main(string[] args) { // certificate var certificate = new X509Certificate2(@".p12", "notasecret"); // header var header = new { typ = "JWT", alg = "RS256" }; // claimset var times = GetExpiryAndIssueDate(); var claimset = new { iss = "", 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 { {"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; } } 

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

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

至:

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

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

我现在得到一个看起来像这样的回复:

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

编辑注意:请确保在服务器上具有正确的日期/时间/时区/ dst配置。 将时钟关闭几秒钟将导致invalid_grant错误。 http://www.time.gov将给出美国政府的官方时间,包括UTC。

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