2012-04-07 14 views
89

Mi sento come se stessi prendendo pillole pazze qui. Di solito c'è sempre un milione di librerie e campioni che fluttuano nel web per qualsiasi attività specifica. Sto cercando di implementare l'autenticazione con un "Account servizio" di Google utilizzando i token Web JSON (JWT) come descritto in here.Esiste un esempio di JSON Web Token (JWT) in C#?

Tuttavia, vi sono solo librerie client in PHP, Python e Java. Anche cercando esempi di JWT al di fuori dell'autenticazione di Google, ci sono solo grilli e bozze sul concetto JWT. È davvero così nuovo e forse un sistema proprietario di Google?

Il campione java che è il più vicino che potrei interpretare sembra piuttosto intenso e intimidatorio. Deve esserci qualcosa in C# che potrei almeno iniziare. Qualsiasi aiuto con questo sarebbe fantastico!

+2

Peter ha la risposta. JWT è relativamente nuovo formato del token, che è il motivo per cui i campioni sono ancora un po 'difficile da trovare, ma sta crescendo molto rapidamente, perché JWTs sono un sostituto tanto necessaria per SWTS. Microsoft sta eseguendo il backup del formato di token, ad esempio le API di connessione live utilizzano JWT. –

+0

Questo ha qualcosa a che fare con App Engine? –

+21

perché diavolo è stata chiusa questa domanda? –

risposta

55

Grazie a tutti. Ho trovato un'implementazione di base di un token Web Json e l'ho espanso con l'aroma di Google. Non ho ancora ottenuto un risultato completo ma è al 97%. Questo progetto ha perso è il vapore, quindi spero che questo aiuterà a qualcun altro ottenere un buon vantaggio temporale:

Nota: cambiamenti che ho fatto per l'implementazione di base (non ricordo dove l'ho trovato,) sono:

  1. Modificato HS256 -> RS256
  2. Scambiato l'ordine JWT e ALG nell'intestazione. Non sei sicuro di chi ha sbagliato, Google o le specifiche, ma Google la prende come è in base ai loro documenti.
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; 
    } 
} 

E poi la mia specifica classe di JWT google:

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

L'implementazione originale sembra essere John Sheehans JWT biblioteca: https://github.com/johnsheehan/jwt –

+0

Sembra che John non supporta gli algoritmi di crittografia RS (ALG bandiera), ma questa versione fa. – Ryan

+12

Questa versione NON supporta correttamente l'algoritmo di firma RS256! Solo hash l'input con i byte chiave come segreto invece di crittografare correttamente l'hash come dovrebbe essere fatto in PKI. Commuta semplicemente l'etichetta HS256 per l'etichetta RS256 senza la corretta implementazione. –

43

Dopo tutti questi mesi sono passati dopo la domanda originale, ora vale la pena sottolineare che Microsoft ha escogitato una soluzione per conto proprio. Vedi 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 per i dettagli.

+6

il pacchetto nuget in quel blog è deprezzato. Credo che il nuovo è https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/4.0.2.205111437 – Stan

+2

@Stan quel collegamento è grande, ma è impostato su una specifica (e ora si antiquato) versione. Questo punterà sempre all'ultima versione. https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/ –

+1

Alcuni snippet di codice che dimostrano l'utilizzo (codifica/decodifica, simmetrico/asimmetrico) sarebbero molto utili. –

1

Questa è la mia implementazione di (Google) Validazione JWT in .NET. Si basa su altre implementazioni su Stack Overflow e GitHub Gists.

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

Si noti che, al fine di utilizzarlo, è necessario aggiungere un riferimento al pacchetto NuGet System.Net.Http.Formatting.Extension. Senza questo, il compilatore non riconoscerà il metodo ReadAsAsync<>.

Problemi correlati