2012-04-07 31 views
89

Tôi cảm thấy như tôi đang uống thuốc điên ở đây. Thông thường, luôn có một triệu thư viện và các mẫu nổi trên web cho bất kỳ tác vụ cụ thể nào. Tôi đang cố gắng triển khai xác thực bằng "Tài khoản dịch vụ" của Google bằng cách sử dụng JSON Web Tokens (JWT) như được mô tả here.Có bất kỳ ví dụ Mã thông báo Web JSON nào (JWT) trong C# không?

Tuy nhiên, chỉ có các thư viện máy khách trong PHP, Python và Java. Ngay cả khi tìm kiếm các ví dụ JWT bên ngoài xác thực của Google, chỉ có dế và bản nháp trên khái niệm JWT. Hệ thống này có thực sự mới và có thể là một hệ thống độc quyền của Google không?

Mẫu java gần nhất mà tôi có thể quản lý để diễn đạt trông khá chuyên sâu và đáng sợ. Có phải là một cái gì đó ra có trong C# mà tôi có thể ít nhất là bắt đầu với. Bất kỳ trợ giúp với điều này sẽ là tuyệt vời!

+2

Peter có câu trả lời của bạn. JWT là một định dạng mã thông báo tương đối mới, đó là lý do tại sao các mẫu vẫn còn một chút khó khăn, nhưng nó đang phát triển rất nhanh vì JWTs là một sự thay thế cần thiết cho SWT. Microsoft đang sao lưu định dạng mã thông báo, các API kết nối trực tiếp, ví dụ sử dụng JWT. –

+0

Điều này có liên quan gì đến App Engine không? –

+21

tại sao câu hỏi này lại bị đóng? –

Trả lời

55

Cảm ơn mọi người. Tôi đã tìm thấy việc triển khai cơ sở của Mã thông báo Web Json và mở rộng trên đó bằng hương vị của Google. Tôi vẫn chưa nhận được nó hoàn toàn làm việc ra nhưng nó 97% ở đó. Dự án này mất nó hơi, vì vậy hy vọng điều này sẽ giúp người khác có được một đầu khởi đầu tốt:

Lưu ý: thay đổi tôi thực hiện để việc thực hiện cơ sở (không thể nhớ nơi tôi tìm thấy nó,) là:

  1. Đã thay đổi HS256 -> RS256
  2. Hoán đổi JWT và thứ tự alg trong tiêu đề. Không chắc chắn những người đã nhận nó sai, Google hoặc spec, nhưng google mang nó theo cách Nó là dưới đây theo tài liệu của họ.
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; 
    } 
} 

Và sau đó lớp JWT cụ google của tôi:

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

Triển khai ban đầu có vẻ là John Sheehans JWT thư viện: https://github.com/johnsheehan/jwt –

+0

Có vẻ như John không hỗ trợ thuật toán mã hóa RS (alg flag) nhưng phiên bản này có. – Ryan

+12

Phiên bản này KHÔNG hỗ trợ thuật toán ký RS256 chính xác! Nó chỉ băm đầu vào với các byte chính làm bí mật thay vì mã hóa đúng mã băm như được thực hiện trong PKI. Nó chỉ đơn thuần là chuyển nhãn HS256 cho nhãn RS256 mà không cần thực hiện đúng. –

43

Sau khi tất cả các tháng này đã trôi qua sau câu hỏi ban đầu, bây giờ nó đáng để chỉ ra rằng Microsoft đã nghĩ ra một giải pháp của riêng mình. Xem 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 để biết chi tiết.

+6

gói nuget trong blog đó không được chấp nhận. Tôi tin rằng cái mới là https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/4.0.2.205111437 – Stan

+2

@Stan liên kết đó là tuyệt vời, nhưng được đặt thành một cái cụ thể (và bây giờ đã lỗi thời) phiên bản. Điều này sẽ luôn trỏ đến phiên bản mới nhất. https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/ –

+1

Một số đoạn mã thể hiện cách sử dụng (mã hóa/giải mã, đối xứng/không đối xứng) sẽ rất hữu ích. –

1

Đây là thực hiện của tôi (Google) Xác thực JWT trong .NET. Nó dựa trên các triển khai khác trên Stack Overflow và 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; 
     } 
    } 
} 

Lưu ý rằng, để sử dụng nó, bạn cần thêm tham chiếu vào gói NuGet System.Net.Http.Formatting.Extension. Nếu không có điều này, trình biên dịch sẽ không nhận ra phương thức ReadAsAsync<>.

Các vấn đề liên quan