12

サーバー間メソッドを介して、アプリで有効になっているBigQueryAPIと通信しようとしています。

このGoogleガイドのすべてのチェックボックスをオンにして、C#で可能な限りJWTを構築しました。

そして、Base64Urlは必要なものすべてをエンコードしました。

しかし、私がグーグルから得る唯一の応答は400悪いリクエストです

"error" : "invalid_request"

私はこれらの他のSOの質問から次のすべてを確認しました:

Fiddlerを使用しても同じ結果が得られます。エラーメッセージはイライラするほど詳細に欠けています!他に何を試すことができますか?!これが私のコードです:

class Program
{
    static void Main(string[] args)
    {
        // certificate
        var certificate = new X509Certificate2(@"<Path to my certificate>.p12", "notasecret");

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

        // claimset
        var times = GetExpiryAndIssueDate();
        var claimset = new
        {
            iss = "<email address of the client id of my app>",
            scope = "https://www.googleapis.com/auth/bigquery",
            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;

        Console.WriteLine(jwt);

        var client = new HttpClient();
        var uri = "https://accounts.google.com/o/oauth2/token";
        var post = new Dictionary<string, string>
        {
            {"assertion", jwt},
            {"grant_type", "urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer"}
        };
        var content = new FormUrlEncodedContent(post);
        var result = client.PostAsync(uri, content).Result;

        Console.WriteLine(result);
        Console.WriteLine(result.Content.ReadAsStringAsync().Result);
        Console.ReadLine();
    }

    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;
    }
}
4

2 に答える 2

12

上記のコメントの私の推測は正しかったようです。次の変更を行ってコードを機能させました。

"urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer"

に:

"urn:ietf:params:oauth:grant-type:jwt-bearer"

誤ってダブルエンコードしていたようです。

次のような応答が返されます。

{
  "access_token" : "1/_5pUwJZs9a545HSeXXXXXuNGITp1XtHhZXXxxyyaacqkbc",
  "token_type" : "Bearer",
  "expires_in" : 3600
}

編集済み注:サーバーで正しい日付/時刻/タイムゾーン/夏時間の構成になっていることを確認してください。クロックを数秒でもオフにすると、invalid_grantエラーが発生します。 http://www.time.govは、UTCを含め、米国政府からの公式時刻を提供します。

于 2012-08-13T21:03:46.693 に答える
6

GetExpiryAndIssueDateメソッドでは、DateTime.Nowの代わりにDateTime.UtcNowを使用してください。

于 2012-09-04T14:05:44.397 に答える