Вопрос по c# – Есть ли какой-нибудь пример JSON Web Token (JWT) в C #?

97

Я чувствую, что принимаю сумасшедшие таблетки здесь. Обычно в сети всегда есть миллион библиотек и примеров для любой конкретной задачи. Я пытаюсь выполнить аутентификацию с помощью "Сервисной учетной записи Google". с помощью веб-токенов JSON (JWT), как описаноВот.

Однако есть только клиентские библиотеки в PHP, Python и Java. Даже при поиске примеров JWT вне аутентификации Google в концепции JWT присутствуют только крикеты и шашки. Это действительно такая новая и, возможно, запатентованная система Google?

Пример java, который мне ближе всего удается интерпретировать, выглядит довольно интенсивно и пугающе. В C # должно быть что-то, с чего я мог бы начать, по крайней мере. Любая помощь с этим была бы великолепна!

Возможный дубликатValidating Google OpenID Connect JWT ID Token Thomas
Это как-то связано с App Engine? Nick Johnson
У Питера есть твой ответ. JWT является относительно новым форматом токенов, поэтому образцы все еще немного трудно найти, но он растет очень быстро, потому что JWT - очень необходимая замена SWT. Microsoft поддерживает формат токенов, например, API Live Connect используют JWT. Andrew Lavers

Ваш Ответ

6   ответов
11

Вот рабочий пример:

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

Потребовалось некоторое время, чтобы собрать фрагменты, разбросанные по сети, документы довольно неполные ...

Наконец, решение, которое было действительно подключи и играй. Спасибо вам большое! Это сработало для меня.
19

Я никогда не использовал его, но в 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/ и нажмите «библиотеки».

3
5

Это моя реализация валидации JWT (Google) в .NET. Он основан на других реализациях на основе стека переполнения и GitHub.

using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace QuapiNet.Service
{
    public class JwtTokenValidation
    {
        public async Task<Dictionary<string, X509Certificate2>> FetchGoogleCertificates()
        {
            using (var http = new HttpClient())
            {
                var response = await http.GetAsync("https://www.googleapis.com/oauth2/v1/certs");

                var dictionary = await response.Content.ReadAsAsync<Dictionary<string, string>>();
                return dictionary.ToDictionary(x => x.Key, x => new X509Certificate2(Encoding.UTF8.GetBytes(x.Value)));
            }
        }

        private string CLIENT_ID = "xxx.apps.googleusercontent.com";

        public async Task<ClaimsPrincipal> ValidateToken(string idToken)
        {
            var certificates = await this.FetchGoogleCertificates();

            TokenValidationParameters tvp = new TokenValidationParameters()
            {
                ValidateActor = false, // check the profile ID

                ValidateAudience = true, // check the client ID
                ValidAudience = CLIENT_ID,

                ValidateIssuer = true, // check token came from Google
                ValidIssuers = new List<string> { "accounts.google.com", "https://accounts.google.com" },

                ValidateIssuerSigningKey = true,
                RequireSignedTokens = true,
                IssuerSigningKeys = certificates.Values.Select(x => new X509SecurityKey(x)),
                IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) =>
                {
                    return certificates
                    .Where(x => x.Key.ToUpper() == kid.ToUpper())
                    .Select(x => new X509SecurityKey(x.Value));
                },
                ValidateLifetime = true,
                RequireExpirationTime = true,
                ClockSkew = TimeSpan.FromHours(13)
            };

            JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler();
            SecurityToken validatedToken;
            ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken);

            return cp;
        }
    }
}

Обратите внимание, что для его использования вам необходимо добавить ссылку на пакет NuGet.System.Net.Http.Formatting.Extension, Без этого компилятор не распознаетReadAsAsync<> метод.

Зачем вам нужно установитьIssuerSigningKeys еслиIssuerSigningKeyResolver предоставлен?
@AsifMD Не знаю и не могу проверить это в данный момент. Может быть, это работает без установки IssuerSigningKey. Вам также необходимо изменить код распознавателя для запроса сертификатов, поскольку в противном случае вы получите сообщение об ошибке через несколько дней, когда Google изменит свои сертификаты.
+1 за этот самый простой подход. Подержанные PM & gt; Install-Package System.IdentityModel.Tokens.Jwt - Версия 5.2.4 для поддержки System.IdentityModel.
45

После того, как все эти месяцы прошли после первоначального вопроса, теперь стоит указать, что 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- каркасного 4-5.aspx для деталей.

@ Считается, что эта ссылка великолепна, но она настроена на конкретную (и теперь уже устаревшую) версию. Это всегда будет указывать на последнюю версию.nuget.org/packages/System.IdentityModel.Tokens.Jwt
Некоторые фрагменты кода, демонстрирующие использование (кодирование / декодирование, симметричный / асимметричный), были бы очень полезны.
пакет nuget в этом блоге является устаревшим. Я считаю, что новыйnuget.org/packages/System.IdentityModel.Tokens.Jwt/…
62

Спасибо всем. Я нашел базовую реализацию веб-токена Json и расширил его, добавив Google. Я до сих пор не получил его полностью отработанным, но там 97%. Этот проект потерял свою популярность, так что, надеюсь, это поможет кому-то еще получить хороший старт:

Замечания: Изменения, которые я внес в базовую реализацию (не помню, где я ее нашел):

  1. Changed HS256 -> RS256
  2. Swapped the JWT and alg order in the header. Not sure who got it wrong, Google or the spec, but google takes it the way It is below according to their docs.
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":"[email protected]account.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);
    }
}
Исходная реализация, похоже, представляет собой библиотеку Джона Шиханса:github.com/johnsheehan/jwt
Эта версия НЕ поддерживает алгоритм подписи RS256 правильно! Он только хэширует ввод с байтами ключа в качестве секрета, а не шифрует хэш должным образом, как это должно быть сделано в PKI. Он просто переключает метку HS256 на метку RS256 без надлежащей реализации.
@Levitikon Любая идея, как я могу декодировать private_key, который Google предоставляет в файл JSON? Спасибо
Приведенный выше код частично подвергается описанной ей атаке безопасности:auth0.com/blog/2015/03/31/… Он уязвим для & # x201C; если сервер ожидает токен, подписанный с помощью RSA, но фактически получает токен, подписанный с помощью HMAC, он будет считать, что открытый ключ на самом деле является секретным ключом HMAC. & # X201D;
Похоже, что John не поддерживает алгоритмы шифрования RS (флаг alg), но эта версия поддерживает.

Похожие вопросы