[英]Calling Google OAuth2 from C# To Get Access Token Results in "Invalid Grant"
我正在嘗試將現有應用程序與 Google 的 OAuth2 系統集成,以便更安全地調用 Google 服務,例如 GMail,並遠離 C# 中的標准 SMTP 客戶端發送技術。我已經設置了一個服務帳戶並獲得了 P12 密鑰和所有內容已經到位並且調用將轉到 OAuth 服務器但是當調用“GetResponse”時我不斷收到以下 JSON 響應:
{"error":"invalid_grant","error_description":"Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim."}
下面是我用來進行此調用的 C# 代碼,我也通過 CURL 獲得了相同的結果。每次我使用本地時間或 UTC 時間擺弄“iap”和“exp”值時,我都會收到不同的消息。 似乎我現在擁有的代碼是我應該使用的代碼,但我無法確定為什么我仍然收到這些消息。 根據Google 文檔,我已經確定系統時鍾與 time.google.com 的 Google NTP 同步,但仍然沒有變化。 在進行 web 請求調用時,我在構建 JSON Web 令牌 (JWT) 時做錯了什么?
try
{
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
List<string> jwtSegments = new List<string>();
var jwtHeader = new {
alg = "RS256",
typ = "JWT"
};
int nowTimeInEpochSeconds = (int)Math.Floor((DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds);
var jwtClaimSet = new {
iss = "***********@********.iam.gserviceaccount.com",
scope = "https://www.googleapis.com/auth/gmail.send",
aud = "https://oauth2.googleapis.com/token",
exp = nowTimeInEpochSeconds + 3600,
iat = nowTimeInEpochSeconds
};
string serializedHeader = JsonConvert.SerializeObject(jwtHeader, Formatting.None);
string serializedClaimSet = JsonConvert.SerializeObject(jwtClaimSet, Formatting.None);
byte[] jwtHeaderBytes = Encoding.UTF8.GetBytes(serializedHeader);
byte[] jwtClaimSetBytes = Encoding.UTF8.GetBytes(serializedClaimSet);
string base64EncodedHeader = Base64UrlEncode(jwtHeaderBytes);
string base64EncodedClaimSet = Base64UrlEncode(jwtClaimSetBytes);
jwtSegments.Add(base64EncodedHeader);
jwtSegments.Add(base64EncodedClaimSet);
string jwtRequestToSign = string.Join(".", jwtSegments);
byte[] jwtRequestBytesToSign = Encoding.UTF8.GetBytes(jwtRequestToSign);
X509Certificate2 cert = new X509Certificate2(@"C:\**************.p12", "notasecret");
AsymmetricAlgorithm rsaSignature = cert.PrivateKey;
byte[] signature = rsaCryptoServiceProvider.SignData(jwtRequestBytesToSign, "SHA256");
string jwt = jwtRequestToSign + "." + Base64UrlEncode(signature);
WebRequest webRequestForaccessToken = WebRequest.Create("https://oauth2.googleapis.com/token") as HttpWebRequest;
string accessTokenRequestParameters = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + jwt;
byte[] accessTokenRequestFromJwt = Encoding.UTF8.GetBytes(accessTokenRequestParameters);
string token = string.Empty;
if (webRequestForaccessToken != null)
{
webRequestForaccessToken.Method = "POST";
webRequestForaccessToken.ContentType = "application/x-www-form-urlencoded";
webRequestForaccessToken.ContentLength = accessTokenRequestFromJwt.Length;
using (Stream stream = webRequestForaccessToken.GetRequestStream())
{
stream.Write(accessTokenRequestFromJwt, 0, accessTokenRequestFromJwt.Length);
}
using (HttpWebResponse httpWebResponseForaccessToken = webRequestForaccessToken.GetResponse() as HttpWebResponse)
{
Stream streamForaccessToken = httpWebResponseForaccessToken?.GetResponseStream();
if (streamForaccessToken != null)
{
using (StreamReader streamReaderForForaccessToken = new StreamReader(streamForaccessToken))
{
string jsonUserResponseString = streamReaderForForaccessToken.ReadToEnd();
JObject groupsIoApiUserObject = JObject.Parse(jsonUserResponseString);
token = groupsIoApiUserObject["access_token"].ToString();
}
}
}
}
}
catch (WebException ex)
{
try
{
string resp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
Console.WriteLine(resp);
}
catch (Exception parseException)
{
string m = parseException.Message;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
你的計算方法不好,或者你的服務器時間不正確的兩個原因,可以檢查類似:
int nowTimeInEpochSeconds = DateTimeOffset.UtcNow.AddMinutes(59).ToUnixTimeSeconds()
因此,事實證明有兩件事需要更正。 首先是根據@GAOUL 提供的答案,我沒有正確計算紀元時間(盡管我不得不修改建議的行以刪除“AddMinutes”方法以獲得“iat”字段的正確值)。 然后,“exp”值變成“iat”值加上 3600(1 小時)。
所以我原來的時間計算是從這個開始的:
int nowTimeInEpochSeconds = (int)Math.Floor((DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds);
對此:
int nowTimeInEpochSeconds = (int)Math.Floor((double)DateTimeOffset.UtcNow.ToUnixTimeSeconds());
我在檢查自己的代碼后發現的第二個問題是我沒有正確地使用私鑰簽名(根本沒有使用私鑰簽名)。
所以我替換了這個代碼分支:
X509Certificate2 cert = new X509Certificate2(@"C:\**************.p12", "notasecret");
AsymmetricAlgorithm rsaSignature = cert.PrivateKey;
byte[] signature = rsaCryptoServiceProvider.SignData(jwtRequestBytesToSign, "SHA256");
用這個分支代替:
X509Certificate2 certificate = new X509Certificate2(@"**************.p12", "notasecret");
RSA rsa = certificate.GetRSAPrivateKey();
byte[] signature = rsa.SignData(jwtRequestBytesToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
應用這些更改后,我從 Google OAuth 服務器收到了有效響應,其中包含訪問/刷新令牌以及其他字段。
另外,感謝@dazwilkin 引用 JWT.io。 那也是一個很大的幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.