具有服务器到服务器身份验证的 Google OAuth2 返回“invalid_grant”
Posted
技术标签:
【中文标题】具有服务器到服务器身份验证的 Google OAuth2 返回“invalid_grant”【英文标题】:Google OAuth2 with Server to Server authentication returns "invalid_grant" 【发布时间】:2015-03-23 01:34:04 【问题描述】:我正在尝试按照here 概述的步骤来获取访问令牌,以便与具有 OAuth2 的 Google 日历 API 一起使用。在尝试汇总并签署 jwt 后,我总是以 400“错误请求”响应结束,并出现错误“invalid_grant”。
我非常严格地遵循这些步骤,并多次仔细检查每一行。我还详尽地浏览了我能找到的关于该主题的每一篇文章。在多年在线寻找解决方案之后,我非常困惑,以至于我写了我的第一个 SO 问题。
我已经尝试过的常见解决方案:
1) 我的系统时钟与 ntp 时间同步
2) 我使用的是 iss 的电子邮件,而不是客户端 ID。
3) 我的发行时间和到期时间都是 UTC
4) 我确实查看了 access_type=offline 参数,但它似乎不适用于这种服务器到服务器的场景。
5) 我没有指定prn参数。
6) 各种其他杂项
我知道有 Google 库可以帮助管理此问题,但我有理由说明为什么我需要通过自己签署 jwt 而不使用提供的库来使其正常工作。此外,到目前为止,我看到的许多问题和示例似乎都在使用 accounts.google.com/o/oauth2/auth 作为基本 url,而我上面链接的文档似乎指定请求转到 www。 googleapis.com/oauth2/v3/token 代替(因此似乎许多现有问题可能适用于不同的场景)。无论如何,我完全被难住了,不知道还能尝试什么。这是我的 C# 代码,其中包含一些特定的字符串。
public static string GetBase64UrlEncoded(byte[] input)
string value = Convert.ToBase64String(input);
value = value.Replace("=", string.Empty).Replace('+', '-').Replace('/', '_');
return value;
static void Main(string[] args)
DateTime baseTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
DateTime now = DateTime.Now.ToUniversalTime();
int ticksIat = ((int)now.Subtract(baseTime).TotalSeconds);
int ticksExp = ((int)now.AddMinutes(55).Subtract(baseTime).TotalSeconds);
string jwtHeader = @"""typ"":""JWT"", ""alg"":""RS256""";
string jwtClaimSet = string.Format(@"""iss"":""************-********************************@developer.gserviceaccount.com""," +
@"""scope"":""https://www.googleapis.com/auth/calendar.readonly""," +
@"""aud"":""https://www.googleapis.com/oauth2/v3/token"",""exp"":0,""iat"":1", ticksExp, ticksIat);
byte[] headerBytes = Encoding.UTF8.GetBytes(jwtHeader);
string base64jwtHeader = GetBase64UrlEncoded(headerBytes);
byte[] claimSetBytes = Encoding.UTF8.GetBytes(jwtClaimSet);
string base64jwtClaimSet = GetBase64UrlEncoded(claimSetBytes);
string signingInputString = base64jwtHeader + "." + base64jwtClaimSet;
byte[] signingInputBytes = Encoding.UTF8.GetBytes(signingInputString);
X509Certificate2 pkCert = new X509Certificate2("<path to cert>.p12", "notasecret");
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)pkCert.PrivateKey;
CspParameters cspParam = new CspParameters
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
;
RSACryptoServiceProvider cryptoServiceProvider = new RSACryptoServiceProvider(cspParam) PersistKeyInCsp = false ;
byte[] signatureBytes = cryptoServiceProvider.SignData(signingInputBytes, "SHA256");
string signatureString = GetBase64UrlEncoded(signatureBytes);
string finalJwt = signingInputString + "." + signatureString;
HttpClient client = new HttpClient();
string url = "https://www.googleapis.com/oauth2/v3/token?grant_type=urn%3aietf%3aparams%3aoauth%3agrant-type%3ajwt-bearer&assertion=" + finalJwt;
HttpResponseMessage message = client.PostAsync(url, new StringContent(string.Empty)).Result;
string result = message.Content.ReadAsStringAsync().Result;
这是使用我在我的谷歌账户上设置的谷歌“服务账户”,生成的私钥及其对应的 .p12 文件直接使用。
有人用这种方法来工作吗?我将非常感谢任何帮助!
【问题讨论】:
【参考方案1】:您正在 POST 到令牌端点,但参数是作为查询字符串的一部分发送的。您应该将参数作为 POST 正文中的 URL 表单编码值发送。例如:
var params = new List<KeyValuePair<string, string>>();
params.Add(new KeyValuePair<string, string>("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"));
params.Add(new KeyValuePair<string, string>("assertion", finalJwt));
var content = new FormUrlEncodedContent(pairs);
var message = client.PostAsync(url, content).Result;
【讨论】:
感谢您的快速回复!这确实解决了这个问题。错过了多么尴尬的事情......【参考方案2】:试试这个来获取访问令牌-
String serviceAccountEmail = "xxxxxxx.gserviceaccount.com";
String keyFilePath = System.Web.HttpContext.Current.Server.MapPath("~/Content/Security/file.p12"); ////.p12 file location
if (!File.Exists(keyFilePath))
Console.WriteLine("An Error occurred - Key file does not exist");
return null;
string[] scopes = new string[]
CloudVideoIntelligenceService.Scope.CloudPlatform, ///CloudVideoIntelligence scope
YouTubeService.Scope.YoutubeForceSsl, ///Youtube scope
TranslateService.Scope.CloudTranslation ///Translation scope
;
var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
Scopes = scopes
.FromCertificate(certificate));
var token = Google.Apis.Auth.OAuth2.GoogleCredential.FromServiceAccountCredential(credential).UnderlyingCredential.GetAccessTokenForRequestAsync().Result;//retrive token
return token;
【讨论】:
以上是关于具有服务器到服务器身份验证的 Google OAuth2 返回“invalid_grant”的主要内容,如果未能解决你的问题,请参考以下文章