114

ここでクレイジーな薬を飲んでいるような気がします。通常、Web 上には、特定のタスク用に常に 100 万のライブラリとサンプルが存在します。ここで説明されているように、JSON Web トークン (JWT) を使用して、Google の「サービス アカウント」で認証を実装しようとしています。

ただし、クライアント ライブラリは PHP、Python、および Java のみです。Google の認証以外で JWT の例を探しても、JWT の概念に関するコオロギや草案しかありません。これは本当に新しく、おそらく Google 独自のシステムなのでしょうか?

私が解釈できる最も近いJavaサンプルは、かなり集中的で威圧的です。C# には、少なくとも開始できる何かがあるはずです。これについての助けは素晴らしいでしょう!

4

9 に答える 9

81

みんな、ありがとう。Json Web Token の基本実装を見つけ、それを Google フレーバーで拡張しました。まだ完全には解決していませんが、97% は達成されています。このプロジェクトは勢いを失ったので、他の誰かが有利なスタートを切るのに役立つことを願っています:

注: 基本実装に加えた変更 (見つけた場所を思い出せません) は次のとおりです。

  1. HS256→RS256に変更
  2. ヘッダーの JWT とアルゴリズムの順序を入れ替えました。誰が間違ったのか、Google なのか仕様なのかはわかりませんが、Google はそれを採用しています。ドキュメントによると、以下のとおりです。
public enum JwtHashAlgorithm
{
    RS256,
    HS384,
    HS512
}

public class JsonWebToken
{
    private static Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;

    static JsonWebToken()
    {
        HashAlgorithms = new Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>>
            {
                { JwtHashAlgorithm.RS256, (key, value) => { using (var sha = new HMACSHA256(key)) { return sha.ComputeHash(value); } } },
                { JwtHashAlgorithm.HS384, (key, value) => { using (var sha = new HMACSHA384(key)) { return sha.ComputeHash(value); } } },
                { JwtHashAlgorithm.HS512, (key, value) => { using (var sha = new HMACSHA512(key)) { return sha.ComputeHash(value); } } }
            };
    }

    public static string Encode(object payload, string key, JwtHashAlgorithm algorithm)
    {
        return Encode(payload, Encoding.UTF8.GetBytes(key), algorithm);
    }

    public static string Encode(object payload, byte[] keyBytes, JwtHashAlgorithm algorithm)
    {
        var segments = new List<string>();
        var header = new { alg = algorithm.ToString(), typ = "JWT" };

        byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
        byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
        //byte[] payloadBytes = Encoding.UTF8.GetBytes(@"{"iss":"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com","scope":"https://www.googleapis.com/auth/prediction","aud":"https://accounts.google.com/o/oauth2/token","exp":1328554385,"iat":1328550785}");

        segments.Add(Base64UrlEncode(headerBytes));
        segments.Add(Base64UrlEncode(payloadBytes));

        var stringToSign = string.Join(".", segments.ToArray());

        var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);

        byte[] signature = HashAlgorithms[algorithm](keyBytes, bytesToSign);
        segments.Add(Base64UrlEncode(signature));

        return string.Join(".", segments.ToArray());
    }

    public static string Decode(string token, string key)
    {
        return Decode(token, key, true);
    }

    public static string Decode(string token, string key, bool verify)
    {
        var parts = token.Split('.');
        var header = parts[0];
        var payload = parts[1];
        byte[] crypto = Base64UrlDecode(parts[2]);

        var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
        var headerData = JObject.Parse(headerJson);
        var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
        var payloadData = JObject.Parse(payloadJson);

        if (verify)
        {
            var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
            var keyBytes = Encoding.UTF8.GetBytes(key);
            var algorithm = (string)headerData["alg"];

            var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign);
            var decodedCrypto = Convert.ToBase64String(crypto);
            var decodedSignature = Convert.ToBase64String(signature);

            if (decodedCrypto != decodedSignature)
            {
                throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
            }
        }

        return payloadData.ToString();
    }

    private static JwtHashAlgorithm GetHashAlgorithm(string algorithm)
    {
        switch (algorithm)
        {
            case "RS256": return JwtHashAlgorithm.RS256;
            case "HS384": return JwtHashAlgorithm.HS384;
            case "HS512": return JwtHashAlgorithm.HS512;
            default: throw new InvalidOperationException("Algorithm not supported.");
        }
    }

    // from JWT spec
    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;
    }

    // from JWT spec
    private static byte[] Base64UrlDecode(string input)
    {
        var output = input;
        output = output.Replace('-', '+'); // 62nd char of encoding
        output = output.Replace('_', '/'); // 63rd char of encoding
        switch (output.Length % 4) // Pad with trailing '='s
        {
            case 0: break; // No pad chars in this case
            case 2: output += "=="; break; // Two pad chars
            case 3: output += "="; break; // One pad char
            default: throw new System.Exception("Illegal base64url string!");
        }
        var converted = Convert.FromBase64String(output); // Standard base64 decoder
        return converted;
    }
}

そして、私のGoogle固有のJWTクラス:

public class GoogleJsonWebToken
{
    public static string Encode(string email, string certificateFilePath)
    {
        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; // Expiration time is up to 1 hour, but lets play on safe side

        var payload = new
        {
            iss = email,
            scope = "https://www.googleapis.com/auth/gan.readonly",
            aud = "https://accounts.google.com/o/oauth2/token",
            exp = exp,
            iat = iat
        };

        var certificate = new X509Certificate2(certificateFilePath, "notasecret");

        var privateKey = certificate.Export(X509ContentType.Cert);

        return JsonWebToken.Encode(payload, privateKey, JwtHashAlgorithm.RS256);
    }
}
于 2012-04-11T13:23:21.640 に答える
47

最初の質問から数か月が経過した今、Microsoft が独自の解決策を考案したことを指摘しておく価値があります。http://blogs.msdn.com/b/vbertocci/archive/2012/11/20/introducing-the-developer-preview-of-the-json-web-token-handler-for-the-microsoft-netを参照してください。詳細については、 -framework-4-5.aspxを参照してください。

于 2012-11-29T06:19:14.533 に答える
21

使ったことはありませんが、NuGet には JWT の実装があります。

パッケージ: https://nuget.org/packages/JWT

ソース: https://github.com/johnsheehan/jwt

.NET 4.0 互換: https://www.nuget.org/packages/jose-jwt/

https://jwt.io/にアクセスして、「ライブラリ」をクリックすることもできます。

于 2012-05-10T14:02:39.920 に答える
12

これが実際の例です:

http://zavitax.wordpress.com/2012/12/17/logging-in-with-google-service-account-in-c-jwt/

ウェブ上に散らばっている断片を集めるのにかなりの時間がかかりました。ドキュメントはかなり不完全です...

于 2012-12-17T13:54:28.787 に答える
2

.NET用のGoogleクライアントライブラリをご覧ください。

于 2012-04-07T16:51:23.900 に答える
2

コードをゼロから書くよりも、標準の有名なライブラリを使用する方がよいでしょう。

  1. JWTトークンをエンコードおよびデコードするための JWT
  2. Bouncy Castleは暗号化と復号化をサポートしています。特に RS256はこちらから入手できます

これらのライブラリを使用すると、以下のように JWT トークンを生成し、RS256 を使用して署名できます。

public string GenerateJWTToken(string rsaPrivateKey)
{
    var rsaParams = GetRsaParameters(rsaPrivateKey);
    var encoder = GetRS256JWTEncoder(rsaParams);

    // create the payload according to the Google's doc
    var payload = new Dictionary<string, object>
    {
        { "iss", ""},
        { "sub", "" },
        // and other key-values according to the doc
    };

    // add headers. 'alg' and 'typ' key-values are added automatically.
    var header = new Dictionary<string, object>
    {
        { "kid", "{your_private_key_id}" },
    };

    var token = encoder.Encode(header,payload, new byte[0]);

    return token;
}

private static IJwtEncoder GetRS256JWTEncoder(RSAParameters rsaParams)
{
    var csp = new RSACryptoServiceProvider();
    csp.ImportParameters(rsaParams);

    var algorithm = new RS256Algorithm(csp, csp);
    var serializer = new JsonNetSerializer();
    var urlEncoder = new JwtBase64UrlEncoder();
    var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

    return encoder;
}

private static RSAParameters GetRsaParameters(string rsaPrivateKey)
{
    var byteArray = Encoding.ASCII.GetBytes(rsaPrivateKey);
    using (var ms = new MemoryStream(byteArray))
    {
        using (var sr = new StreamReader(ms))
        {
            // use Bouncy Castle to convert the private key to RSA parameters
            var pemReader = new PemReader(sr);
            var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
            return DotNetUtilities.ToRSAParameters(keyPair.Private as RsaPrivateCrtKeyParameters);
        }
    }
}

ps: RSA 秘密鍵は次の形式である必要があります。

-----BEGIN RSA PRIVATE KEY-----
 {base64 formatted value}
-----END RSA PRIVATE KEY-----
于 2019-05-22T03:43:09.483 に答える