C #'da JSON Web Token (JWT) örneği var mı?


103

Burada çılgın haplar alıyormuş gibi hissediyorum. Genellikle herhangi bir görev için her zaman bir milyon kitaplık ve web'de dolaşan örnek vardır. Burada açıklandığı gibi JSON Web Jetonlarını (JWT) kullanarak bir Google "Hizmet Hesabı" ile kimlik doğrulamayı uygulamaya çalışıyorum .

Ancak PHP, Python ve Java'da yalnızca istemci kitaplıkları vardır. Google'ın kimlik doğrulaması dışında JWT örnekleri ararken bile, JWT konseptinde sadece cırcır böcekleri ve taslaklar vardır. Bu gerçekten çok yeni ve muhtemelen Google'ın tescilli bir sistemi mi?

Yorumlayabildiğim en yakın java örneği oldukça yoğun ve göz korkutucu görünüyor. C # da en azından başlayabileceğim bir şey olmalı. Bununla ilgili herhangi bir yardım harika olur!


2
Cevabınız Peter'da. JWT, nispeten yeni bir belirteç formatıdır, bu nedenle örneklerin bulunması hala biraz zordur, ancak çok hızlı büyüyor çünkü JWT'ler, SWT'ler için çok gerekli bir yedek. Microsoft belirteç biçimini destekliyor, örneğin canlı bağlantı API'leri JWT'leri kullanıyor.
Andrew Lavers

Bunun App Engine ile bir ilgisi var mı?
Nick Johnson

Yanıtlar:


76

Herkese teşekkürler. Bir Json Web Jetonunun temel uygulamasını buldum ve bunu Google çeşidiyle genişlettim. Hala tam olarak çözemedim ama% 97 orada. Bu proje buharını kaybetti, bu yüzden umarım bu başka birinin iyi bir başlangıç ​​yapmasına yardımcı olur:

Not: Temel uygulamada yaptığım değişiklikler (nerede bulduğumu hatırlayamıyorum):

  1. HS256 -> RS256 değiştirildi
  2. Başlıktaki JWT ve alg sırasını değiştirdi. Kimin yanlış anladığından emin değilim, Google veya spesifikasyon, ancak Google, belgelerine göre aşağıdaki gibi alıyor.
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;
    }
}

Ve sonra Google'a özel JWT sınıfım:

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

9
Orijinal uygulama John Sheehans JWT kütüphanesi gibi görünüyor: github.com/johnsheehan/jwt
Torbjørn

Görünüşe göre John, RS şifreleme algoritmalarını (alg bayrağı) desteklemiyor, ancak bu sürüm destekliyor.
Ryan

15
Bu sürüm, RS256 imzalama algoritmasını doğru şekilde DESTEKLEMEZ! PKI'da yapılması gerektiği gibi, karmayı doğru şekilde şifrelemek yerine, yalnızca anahtar baytları ile girdiyi gizli olarak karma hale getirir. Yalnızca RS256 etiketi için HS256 etiketini doğru uygulama olmadan değiştirir.
Hans Z.

1
Yukarıdaki kod kısmen kendisi tarafından tanımlanan güvenlik saldırısına tabidir: auth0.com/blog/2015/03/31/… "Bir sunucu RSA ile imzalanmış bir belirteç bekliyor, ancak aslında HMAC ile imzalanmış bir belirteç alıyorsa , genel anahtarın aslında bir HMAC gizli anahtarı olduğunu düşünecektir. "
BennyBechDk

@Levitikon Herhangi bir fikir, Google'ın JSON dosyasında sağladığı private_key'nin kodunu nasıl çözebilirim? Teşekkürler
bibscy

46

İlk sorunun üzerinden geçen bunca aydan sonra, şimdi Microsoft'un kendi başına bir çözüm geliştirdiğini belirtmekte fayda var. Bkz. Http://blogs.msdn.com/b/vbertocci/archive/2012/11/20/introducing-the-developer-preview-of-the-json-web-token-handler-for-the-microsoft-net Ayrıntılar için -framework-4-5.aspx .


7
o blogdaki nuget paketi amortismana tabi. Yenisinin nuget.org/packages/System.IdentityModel.Tokens.Jwt/…
Stan

3
@Stan bu bağlantı harika, ancak belirli (ve artık tarihi geçmiş) bir sürüme ayarlandı. Bu her zaman en son sürüme işaret edecektir. nuget.org/packages/System.IdentityModel.Tokens.Jwt
Jeffrey Harmon

3
Kullanımı gösteren bazı kod parçacıkları (kodlama / kod çözme, simetrik / asimetrik) çok faydalı olacaktır.
Ohad Schneider



6

Bu benim .NET'teki (Google) JWT Doğrulaması uygulamamdır. Stack Overflow ve GitHub esaslarındaki diğer uygulamalara dayanmaktadır.

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

Bunu kullanmak için NuGet paketine bir başvuru eklemeniz gerektiğini unutmayın System.Net.Http.Formatting.Extension. Bu olmadan, derleyici ReadAsAsync<>yöntemi tanımayacaktır .


Neden sette gerekir IssuerSigningKeyseğer IssuerSigningKeyResolversağlanır?
AsifM

@AsifMD Gerçekten bilmiyorum ve şu anda test edemiyorum. Belki IssuerSigningKey'i ayarlamadan çalışır. Ayrıca, sertifikaları istemek için çözümleyici kodunu değiştirmeniz gerekir, çünkü birkaç gün içinde Google sertifikalarını değiştirdiğinde bir hata alırsınız.
Thomas

Bu en basit yaklaşım için +1. System.IdentityModel'i desteklemek için PM> Install-Package System.IdentityModel.Tokens.Jwt -Version 5.2.4 kullanıldı
Karthick Jayaraman


1

Kodu sıfırdan yazmak yerine standart ve ünlü kütüphaneleri kullanmak daha iyi olacaktır.

  1. JWT belirteçlerini kodlamak ve çözmek için JWT
  2. Bouncy Castle , şifreleme ve şifre çözmeyi destekler, özellikle RS256 buradan alın

Bu kitaplıkları kullanarak bir JWT belirteci oluşturabilir ve aşağıdaki gibi RS256 kullanarak imzalayabilirsiniz.

    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 özel anahtarı aşağıdaki biçime sahip olmalıdır:

----- BAŞLANGIÇ RSA ÖZEL ANAHTAR ----- {base64 biçimlendirilmiş değer} ----- END RSA ÖZEL ANAHTAR -----


0

Burada, G Suite Kullanıcılarına ve Gruplarına erişen ve JWT aracılığıyla kimlik doğrulaması yapan Google Hizmet Hesapları için yalnızca REST için geçerli olan başka bir örnek verilmiştir . Bu API'ların Google dokümantasyonu korkunç olmanın ötesinde olduğundan, bu yalnızca Google kitaplıklarının yansımasıyla mümkün oldu . MS teknolojilerinde kod yazmaya alışkın olan herkes, Google hizmetlerinde her şeyin nasıl bir araya geldiğini anlamakta zorlanacaktır.

$iss = "<name>@<serviceaccount>.iam.gserviceaccount.com"; # The email address of the service account.
$sub = "impersonate.user@mydomain.com"; # The user to impersonate (required).
$scope = "https://www.googleapis.com/auth/admin.directory.user.readonly https://www.googleapis.com/auth/admin.directory.group.readonly";
$certPath = "D:\temp\mycertificate.p12";
$grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer";

# Auxiliary functions
function UrlSafeEncode([String] $Data) {
    return $Data.Replace("=", [String]::Empty).Replace("+", "-").Replace("/", "_");
}

function UrlSafeBase64Encode ([String] $Data) {
    return (UrlSafeEncode -Data ([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Data))));
}

function KeyFromCertificate([System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
    $privateKeyBlob = $Certificate.PrivateKey.ExportCspBlob($true);
    $key = New-Object System.Security.Cryptography.RSACryptoServiceProvider;
    $key.ImportCspBlob($privateKeyBlob);
    return $key;
}

function CreateSignature ([Byte[]] $Data, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
    $sha256 = [System.Security.Cryptography.SHA256]::Create();
    $key = (KeyFromCertificate $Certificate);
    $assertionHash = $sha256.ComputeHash($Data);
    $sig = [Convert]::ToBase64String($key.SignHash($assertionHash, "2.16.840.1.101.3.4.2.1"));
    $sha256.Dispose();
    return $sig;
}

function CreateAssertionFromPayload ([String] $Payload, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
    $header = @"
{"alg":"RS256","typ":"JWT"}
"@;
    $assertion = New-Object System.Text.StringBuilder;

    $assertion.Append((UrlSafeBase64Encode $header)).Append(".").Append((UrlSafeBase64Encode $Payload)) | Out-Null;
    $signature = (CreateSignature -Data ([System.Text.Encoding]::ASCII.GetBytes($assertion.ToString())) -Certificate $Certificate);
    $assertion.Append(".").Append((UrlSafeEncode $signature)) | Out-Null;
    return $assertion.ToString();
}

$baseDateTime = New-Object DateTime(1970, 1, 1, 0, 0, 0, [DateTimeKind]::Utc);
$timeInSeconds = [Math]::Truncate([DateTime]::UtcNow.Subtract($baseDateTime).TotalSeconds);

$jwtClaimSet = @"
{"scope":"$scope","email_verified":false,"iss":"$iss","sub":"$sub","aud":"https://oauth2.googleapis.com/token","exp":$($timeInSeconds + 3600),"iat":$timeInSeconds}
"@;


$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "notasecret", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable);
$jwt = CreateAssertionFromPayload -Payload $jwtClaimSet -Certificate $cert;


# Retrieve the authorization token.
$authRes = Invoke-WebRequest -Uri "https://oauth2.googleapis.com/token" -Method Post -ContentType "application/x-www-form-urlencoded" -UseBasicParsing -Body @"
assertion=$jwt&grant_type=$([Uri]::EscapeDataString($grantType))
"@;
$authInfo = ConvertFrom-Json -InputObject $authRes.Content;

$resUsers = Invoke-WebRequest -Uri "https://www.googleapis.com/admin/directory/v1/users?domain=<required_domain_name_dont_trust_google_documentation_on_this>" -Method Get -Headers @{
    "Authorization" = "$($authInfo.token_type) $($authInfo.access_token)"
}

$users = ConvertFrom-Json -InputObject $resUsers.Content;

$users.users | ft primaryEmail, isAdmin, suspended;

0

İşte sınıfların ve işlevlerin listesi:

open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.Logging
open Microsoft.AspNetCore.Authorization
open Microsoft.AspNetCore.Authentication
open Microsoft.AspNetCore.Authentication.JwtBearer
open Microsoft.IdentityModel.Tokens
open System.IdentityModel.Tokens
open System.IdentityModel.Tokens.Jwt
open Microsoft.IdentityModel.JsonWebTokens
open System.Text
open Newtonsoft.Json
open System.Security.Claims
    let theKey = "VerySecretKeyVerySecretKeyVerySecretKey"
    let securityKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes(theKey))
    let credentials = SigningCredentials(securityKey, SecurityAlgorithms.RsaSsaPssSha256)
    let expires = DateTime.UtcNow.AddMinutes(123.0) |> Nullable
    let token = JwtSecurityToken(
                    "lahoda-pro-issuer", 
                    "lahoda-pro-audience",
                    claims = null,
                    expires =  expires,
                    signingCredentials = credentials
        )

    let tokenString = JwtSecurityTokenHandler().WriteToken(token)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.