使用服务器到服务器身份验证的Google OAuth2返回“invalid_grant”

时间:2015-01-23 22:14:56

标签: c# oauth-2.0 google-api google-oauth jwt

我正在尝试按照此处列出的步骤https://developers.google.com/accounts/docs/OAuth2ServiceAccount#overview获取访问令牌,以便与使用OAuth2的Google Calendar API一起使用。在试图组装并签署jwt之后,我总是得到一个400" Bad request"响应,错误" invalid_grant"。

我已经非常仔细地按照这些步骤进行了多次细致检查。我还详尽地浏览了我能找到的每个主题。经过多年在线寻找解决方案,我已经写下了我的第一个问题。

我已经尝试过的常用解决方案:

1)我的系统时钟与ntp时间同步

2)我使用的是电子邮件,而不是客户端ID。

3)我发布的时间和到期时间是UTC

4)我确实查看了access_type = offline参数,但它似乎不适用于此服务器到服务器方案。

5)我没有指定prn参数。

6)各种其他错误的事情

我知道有谷歌图书馆可以帮助管理这个,但我有理由说明为什么我需要通过在不使用提供的库的情况下自己签署jwt来实现这一目标。此外,我到目前为止看到的许多问题和示例似乎都使用accounts.google.com/o/oauth2/auth作为基本网址,而我上面链接的文档似乎指明请求转到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;
    }

这是使用谷歌&#34;服务帐户&#34;我在我的Google帐户上设置了一个生成的私钥及其直接使用的相应.p12文件。

有没有人有这种方法工作?我非常感谢任何帮助!

2 个答案:

答案 0 :(得分:2)

您正在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; 

答案 1 :(得分:0)

尝试获取访问令牌-

            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;