Google API OAuth2.0 JWT 返回 400 - 错误请求

Posted

技术标签:

【中文标题】Google API OAuth2.0 JWT 返回 400 - 错误请求【英文标题】:Google API OAuth2.0 JWT returns 400 - Bad Request 【发布时间】:2012-08-21 22:06:58 【问题描述】:

我从帖子中复制了代码:

Google OAuth2 Service Account Access Token Request gives 'Invalid Request' Response

并尝试让我自己的 OAuth2.0 / JWT 身份验证工作。但无论我尝试做什么,我都会不断收到异常

System.Exception:远程服务器返回错误:(400) Bad Request。

这是我的代码:

public void Authenticate()

    string clientId = "...apps.googleusercontent.com";
    string clientSecret = "...";
    string emailAddress = "...@developer.gserviceaccount.com";
    string publicKeyFingerprints = "...";

    string certificateFilename = "Google Analytics - OAuth 2.0 - ...-privatekey.p12";

    // certificate 
    var certificate = new X509Certificate2(certificateFilename, clientSecret);

    // header 
    var header = new  typ = "JWT", alg = "RS256" ;

    // claimset 
    var times = GetExpiryAndIssueDate();
    var claimset = new
    
        iss = emailAddress,
        scope = "https://www.googleapis.com/auth/analytics.readonly",
        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;

    _logger.DebugFormat("JWT: 0", jwt);

    var r = WebRequest.Create("https://accounts.google.com/o/oauth2/token") as HttpWebRequest;
    r.Method = "POST";
    r.ContentType = "application/x-www-form-urlencoded";

    var post = string.Format("0=1&2=3",
        "grant_type", HttpUtility.UrlEncode("urn:ietf:params:oauth:grant-type:jwt-bearer", Encoding.UTF8),
        "assertion" , jwt);

    var result = SendHttpRequest(r, post);

    _logger.Debug(result);


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;


private string SendHttpRequest(HttpWebRequest request, string json)

    string result = string.Empty;
    try
    
            _logger.DebugFormat("HttpRequest: 0 1", request.Method, request.RequestUri);
            foreach (string header in request.Headers)
            
                _logger.DebugFormat("Header[0]: 1", header, request.Headers[header]);
            
            _logger.DebugFormat("Body: 0", json);

            byte[] body = Encoding.UTF8.GetBytes(json);
            request.ContentLength = body.Length;

            using (Stream requestStream = request.GetRequestStream())
            
                requestStream.Write(body, 0, body.Length);
            

            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            using (Stream receiveStream = response.GetResponseStream())
            
                using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
                
                    result = readStream.ReadToEnd();
                
            
            _logger.DebugFormat("...done, result=0", result);
    
    catch (Exception ex)
    
        throw new Exception(ex.Message);
    
    return result;

有人知道这段代码有什么问题吗?

【问题讨论】:

【参考方案1】:

我得到了代码:

var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var issueTime = DateTime.Now.ToUniversalTime();

我添加了 .ToUniversalTime(),现在我获得了访问令牌。

【讨论】:

【参考方案2】:

请查看回复的正文。这通常会更详细地说明 400 错误背后的原因。

如果您复制了该帖子中的代码,您是否验证了服务器上的日期/时间是否正确?这是作为 JWT 的一部分进行签名时最常见的错误原因。即使是几秒钟的时间 - 验证您的时钟是否已同步到 NTP 和/或与 time.gov 匹配。还要确保时区正确。

【讨论】:

我无法将时钟设置为与 NTP 同步,因为它是域的一部分。 我的系统时间与 www.worldtimeserver.com 上的时间相同。我的时区是 GTM+1。我需要在代码中的某处指定吗? 无需在代码中指定。您的代码应该从您的系统中获取它。【参考方案3】:

我也遇到了同样的问题

返回 400 - 错误请求

错误:invalid_grant

错误描述:错误请求

这背后的主要原因是请求令牌的服务器中的时区或日期和时间不正确 根据您的时区更改它会正常工作

它对我有用!

【讨论】:

以上是关于Google API OAuth2.0 JWT 返回 400 - 错误请求的主要内容,如果未能解决你的问题,请参考以下文章

OAuth2.0-JWT令牌

通过 Oauth2.0 向 Google Latitude API 发送用户新位置

授权命令行工具使用 Google API(通过 OAuth2.0 或其他任何方式)

OAuth 2.0 与 JWT

OAuth2.0 - 使用JWT替换Token 及 JWT内容增强

将 OAuth 2.0 和 Google 电子表格 API 与 Java 结合使用的示例是啥?