2012-04-07 48 views
89

Siento que estoy tomando pastillas locas aquí. Por lo general, siempre hay un millón de bibliotecas y muestras flotando en la web para cualquier tarea determinada. Estoy intentando implementar la autenticación con una "Cuenta de servicio" de Google mediante el uso de JSON Web Tokens (JWT) como se describe en here.¿Hay algún ejemplo de JSON Web Token (JWT) en C#?

Sin embargo, solo hay bibliotecas de cliente en PHP, Python y Java. Incluso buscando ejemplos JWT fuera de la autenticación de Google, solo hay grillos y borradores en el concepto JWT. ¿Es esto realmente nuevo y posiblemente un sistema propietario de Google?

La muestra de Java que es lo más cerca que pude interpretar parece bastante intensa e intimidante. Tiene que haber algo en C# con el que al menos pueda empezar. ¡Cualquier ayuda con esto sería genial!

+2

Peter tiene su respuesta. JWT es un formato de token relativamente nuevo, por lo que las muestras aún son un poco difíciles de conseguir, pero está creciendo muy rápidamente porque los JWT son un reemplazo muy necesario para los SWT. Microsoft está respaldando el formato de token, las API de conexión en vivo, por ejemplo, usa JWT. –

+0

¿Tiene esto algo que ver con App Engine? –

+21

¿Por qué diablos fue esta pregunta cerrada? –

Respuesta

55

Gracias a todos. Encontré una implementación básica de un Json Web Token y lo amplié con el sabor de Google. Todavía no lo he trabajado del todo, pero está en un 97%. Este proyecto perdió su vapor, y espero que esto ayude a alguien más tiene una buena ventaja inicial:

Nota: Los cambios que he realizado en la implementación base (no recuerdo donde lo encontré,) son:

  1. Cambiado HS256 -> RS256
  2. cambiado el orden de JWT y ALG en la cabecera. No estoy seguro de quién lo entendió mal, Google o la especificación, pero google lo toma de la manera que se muestra a continuación según sus documentos.
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; 
    } 
} 

Y entonces mi clase específica 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

La implementación original parece ser John Sheehans JWT biblioteca: https://github.com/johnsheehan/jwt –

+0

Parece que John's no admite los algoritmos de encriptación RS (indicador de alg) pero esta versión sí. – Ryan

+12

¡Esta versión NO es compatible con el algoritmo de firma RS256 correctamente! Solo hashes la entrada con los bytes clave como el secreto en lugar de cifrar correctamente el hash como debería hacerse en PKI. Simplemente cambia la etiqueta HS256 por la etiqueta RS256 sin la implementación adecuada. –

43

Después de todos estos meses después de la pregunta original, ahora vale la pena señalar que Microsoft ha ideado una solución propia. Vea 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 para más detalles.

+6

el paquete nuget en ese blog se deprecia. Creo que el nuevo es https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/4.0.2.205111437 – Stan

+2

@Stan ese enlace es genial, pero está configurado para un sitio específico (y ahora obsoleto)) versión. Esto siempre apuntará a la última versión. https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/ –

+1

Algunos fragmentos de código que demuestran el uso (codificación/descodificación, simétrico/asimétrico) serían muy útiles. –

1

Ésta es mi implementación de (Google) JWT Validation en .NET. Se basa en otras implementaciones en Stack Overflow y 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; 
     } 
    } 
} 

Tenga en cuenta que, con el fin de utilizarlo, es necesario agregar una referencia al paquete NuGet System.Net.Http.Formatting.Extension. Sin esto, el compilador no reconocerá el método ReadAsAsync<>.

Cuestiones relacionadas