C# 带有服务器到服务器身份验证的Google OAuth2返回;无效的“授权”;

C# 带有服务器到服务器身份验证的Google OAuth2返回;无效的“授权”;,c#,oauth-2.0,google-api,google-oauth,jwt,C#,Oauth 2.0,Google Api,Google Oauth,Jwt,我试图按照概述的步骤获取访问令牌,以便与带有OAuth2的Google日历API一起使用。在尝试组合并签署jwt之后,我总是得到400个“错误请求”响应,错误为“无效的授权” 我已经非常仔细地遵循了这些步骤,并且仔细地检查了每一行多次。我也仔细阅读了我能找到的每一篇关于这个主题的帖子。多年来,我一直在网上寻找解决方案,现在我已经写下了我的第一个问题 我已经尝试过的常见建议解决方案: 1) 我的系统时钟与ntp时间同步 2) 我使用的是iss的电子邮件,而不是客户ID 3) 我的发布时间和到期时间

我试图按照概述的步骤获取访问令牌,以便与带有OAuth2的Google日历API一起使用。在尝试组合并签署jwt之后,我总是得到400个“错误请求”响应,错误为“无效的授权”

我已经非常仔细地遵循了这些步骤,并且仔细地检查了每一行多次。我也仔细阅读了我能找到的每一篇关于这个主题的帖子。多年来,我一直在网上寻找解决方案,现在我已经写下了我的第一个问题

我已经尝试过的常见建议解决方案:

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;
    }
公共静态字符串GetBase64UrlEncoded(字节[]输入)
{
字符串值=Convert.tobase64字符串(输入);
value=value.Replace(“=”,string.Empty).Replace(“+”,“-”).Replace(“/”,“"”);
返回值;
}
静态void Main(字符串[]参数)
{                       
DateTime baseTime=新的日期时间(1970,1,1,0,0,0,0,DateTimeKind.Utc);
DateTime now=DateTime.now.ToUniversalTime();
int ticksIat=((int)now.Subtract(baseTime.TotalSeconds);
int ticksepp=((int)now.AddMinutes(55).Subtract(baseTime.TotalSeconds);
字符串jwtHeader=@“{”类型“:”JWT“,”alg“:”RS256“}”;
string jwtClaimSet=string.Format(@“{”iss“:”***************-**************************************************@developer.gserviceaccount.com“)+
@“范围”:https://www.googleapis.com/auth/calendar.readonly""," +
@“澳元”:https://www.googleapis.com/oauth2/v3/token","exp":{0},"iat":{1}},ticksepp,ticksIat);;
byte[]headerBytes=Encoding.UTF8.GetBytes(jwtHeader);
字符串base64jwtHeader=GetBase64UrlEncoded(headerBytes);
byte[]claimSetBytes=Encoding.UTF8.GetBytes(jwtClaimSet);
字符串base64jwtClaimSet=GetBase64UrlEncoded(claimSetBytes);
字符串signingInputString=base64jwtHeader+“”+base64jwtClaimSet;
byte[]signingInputBytes=Encoding.UTF8.GetBytes(signingInputString);
X509Certificate2 pkCert=新的X509Certificate2(“.p12”,“notasecret”);
RSACryptoServiceProvider rsa=(RSACryptoServiceProvider)pkCert.PrivateKey;
CspParameters cspParam=新的CspParameters
{
KeyContainerName=rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber=rsa.CspKeyContainerInfo.KeyNumber==KeyNumber.Exchange?1:2
};
RSACryptoServiceProvider cryptoServiceProvider=新的RSACryptoServiceProvider(cspParam){PersistKeyInCsp=false};
byte[]signatureBytes=cryptoServiceProvider.SignData(signingInputBytes,“SHA256”);
字符串signatureString=GetBase64UrlEncoded(signatureBytes);
字符串finalJwt=signingInputString+“”+signatureString;
HttpClient=新的HttpClient();
字符串url=”https://www.googleapis.com/oauth2/v3/token?grant_type=urn%3aietf%3aparams%3aoauth%3agrant-类型%3ajwt承载和断言=“+finalJwt;
HttpResponseMessage=client.PostAsync(url,new StringContent(string.Empty)).Result;
字符串结果=message.Content.ReadAsStringAsync().result;
}
这是使用我在谷歌账户上设置的谷歌“服务账户”,生成的私钥及其对应的.p12文件直接使用


有人用这种方法工作吗?我将非常感谢任何帮助

您正在过帐到令牌端点,但参数作为查询字符串的一部分发送。您应该在帖子正文中以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; 
var params=new List();
添加(新的KeyValuePair(“grant_类型”,“urn:ietf:params:oauth:grant类型:jwt承载”);
添加(新的KeyValuePair(“断言”,finalJwt));
var内容=新FormUrlEncodedContent(成对);
var message=client.PostAsync(url、内容).Result;

尝试此操作以获取访问令牌-

            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;

谢谢你的快速回复!这确实解决了问题。错过了一件多么尴尬的事。。。