首页 文章

C#中是否有任何JSON Web Token(JWT)示例?

提问于
浏览
95

对于任何给定的任务,我觉得我总是有一百万个库和样本浮动在网络上 . 我正在尝试使用JSON Web令牌(JWT)使用Google "Service Account"实现身份验证,如here所述 .

但是,只有PHP,Python和Java中的客户端库 . 即使在Google认证之外搜索JWT示例,JWT概念也只有蟋蟀和草稿 . 这真的是新的,可能是谷歌专有系统吗?

我能设法解释的最接近的java样本看起来非常密集和令人生畏 . 在C#中必须有一些我至少可以开始的东西 . 任何帮助都会很棒!

6 回答

  • 44

    感谢大家 . 我找到了Json Web Token的基本实现,并使用Google风格进行了扩展 . 我还没有完全解决它,但那里有97% . 这个项目失去了它的蒸汽,所以希望这将有助于其他人获得良好的开端:

    注意:我对基础实现所做的更改(不记得我在哪里找到它)是:

    更改了HS256 - > RS256在 Headers 中交换了JWT和alg顺序 . 不确定是谁弄错了,谷歌或规范,但谷歌采取它的方式根据他们的文档下面 .

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

    然后我的谷歌特定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);
        }
    }
    
  • 17

    经过这几个月的原始问题之后,现在值得指出微软已经设计出了自己的解决方案 . 有关详细信息,请参见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 .

  • 10

    我从来没有使用它,但在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/并单击"libraries" .

  • 2

    这是一个工作示例:

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

    收集分散在网络上的文章需要相当长的时间,文档相当不完整......

  • 4

    这是我在.NET中实现(Google)JWT验证 . 它基于Stack Overflow和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<> 方法 .

  • 60

相关问题