【问题标题】:Is there any JSON Web Token (JWT) example in C#?C# 中是否有任何 JSON Web 令牌 (JWT) 示例?
【发布时间】:2012-04-20 18:15:14
【问题描述】:

我觉得我在这里服用了疯狂的药丸。通常,对于任何给定的任务,网络上总是有一百万个库和样本。我正在尝试使用here 所述的 JSON Web 令牌 (JWT) 来使用 Google“服务帐户”实现身份验证。

但是,只有 PHP、Python 和 Java 中的客户端库。即使在 Google 的身份验证之外搜索 JWT 示例,关于 JWT 的概念也只有蟋蟀和草稿。这真的很新,而且可能是谷歌专有系统吗?

我能解释的最接近的 java 示例看起来非常密集且令人生畏。 C# 中必须有一些我至少可以开始的东西。对此的任何帮助都会很棒!

【问题讨论】:

  • 彼得有你的答案。 JWT 是一种相对较新的令牌格式,这就是为什么样本仍然有点难以获得的原因,但它的增长非常迅速,因为 JWT 是 SWT 急需的替代品。 Microsoft 支持令牌格式,例如实时连接 API 使用 JWT。
  • 这和 App Engine 有关系吗?

标签: c# oauth oauth-2.0 jwt


【解决方案1】:

谢谢大家。我找到了一个 Json Web Token 的基本实现,并用 Google 风格对其进行了扩展。我还没有完全解决它,但它已经完成了 97%。这个项目失去了动力,所以希望这能帮助其他人获得良好的开端:

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

  1. 已更改 HS256 -> RS256
  2. 交换了标头中的 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);
    }
}

【讨论】:

  • 原来的实现好像是John Sheehans JWT库:github.com/johnsheehan/jwt
  • 看起来 John's 不支持 RS 加密算法(alg 标志),但这个版本支持。
  • 此版本不正确支持RS256签名算法!它仅使用密钥字节作为秘密对输入进行散列,而不是像在 PKI 中那样正确加密散列。它只是将 HS256 标签转换为 RS256 标签,而没有正确实现。
  • 上面的代码部分受到了她描述的安全攻击:auth0.com/blog/2015/03/31/… 它很容易受到“如果服务器期望一个用 RSA 签名的令牌,但实际上收到一个用 HMAC 签名的令牌,它会认为公钥实际上是 HMAC 密钥。”
  • @Levitikon 任何想法如何解码谷歌在 JSON 文件中提供的 private_key?谢谢
【解决方案2】:

在最初的问题已经过去了几个月之后,现在值得指出的是,微软已经设计了自己的解决方案。详情请见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

【讨论】:

【解决方案3】:

我从未使用过它,但在 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/ 并点击“库”。

【讨论】:

    【解决方案4】:

    这是一个工作示例:

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

    收集散落在网络上的碎片花了相当长的时间,文档相当不完整......

    【讨论】:

    • 终于找到了一个真正即插即用的解决方案。太感谢了!这对我有用。
    【解决方案5】:

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

    请注意,为了使用它,您需要添加对 NuGet 包System.Net.Http.Formatting.Extension 的引用。没有这个,编译器将无法识别ReadAsAsync&lt;&gt; 方法。

    【讨论】:

    • 如果提供了IssuerSigningKeyResolver,为什么还需要设置IssuerSigningKeys
    • @AsifMD 真的不知道,目前无法测试。也许它可以在不设置 IssuerSigningKey 的情况下工作。您还需要更改解析器代码以请求证书,否则在几天内 Google 更改证书时您会收到错误消息。
    • +1 这个最简单的方法。使用 PM> Install-Package System.IdentityModel.Tokens.Jwt -Version 5.2.4 来支持 System.IdentityModel
    【解决方案6】:

    【讨论】:

      【解决方案7】:

      最好使用标准和著名的库,而不是从头开始编写代码。

      1. JWT 用于编码和解码 JWT 令牌
      2. Bouncy Castle支持加解密,特别是RS256 get it here

      使用这些库,您可以生成 JWT 令牌并使用 RS256 对其进行签名,如下所示。

      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 私钥应采用以下格式:

      -----BEGIN RSA PRIVATE KEY-----
       {base64 formatted value}
      -----END RSA PRIVATE KEY-----
      

      【讨论】:

        【解决方案8】:

        这是另一个 REST-only 工作示例,用于访问 G Suite 用户和组的 Google 服务帐户,通过 JWT 进行身份验证。这只能通过反映 Google 库来实现,因为这些 API 的 Google 文档非常糟糕。任何使用 MS 技术编写代码的人都很难弄清楚 Google 服务中的所有内容是如何组合在一起的。

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

        【讨论】:

          【解决方案9】:

          这是类和函数的列表:

          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)
          

          【讨论】:

            猜你喜欢
            • 2016-08-01
            • 2017-04-16
            • 2012-12-21
            • 2017-09-15
            • 2016-07-20
            • 2018-01-09
            • 2014-11-27
            • 2016-06-06
            • 2023-04-08
            相关资源
            最近更新 更多