简体   繁体   English

C#中有没有JSON Web Token(JWT)的例子?

[英]Is there any JSON Web Token (JWT) example in C#?

I feel like I'm taking crazy pills here.我觉得我在这里吃了疯狂的药。 Usually there's always a million library and samples floating around the web for any given task.对于任何给定的任务,web 周围总是有一百万个库和示例。 I'm trying to implement authentication with a Google "Service Account" by use of JSON Web Tokens (JWT) as described here .我正在尝试通过使用此处所述的 JSON Web 令牌 (JWT) 使用 Google“服务帐户”实施身份验证。

However there is only client libraries in PHP, Python, and Java. Even searching for JWT examples outside of Google's authentication, there is only crickets and drafts on the JWT concept.然而只有PHP、Python、Java中的client libraries。即使在谷歌认证之外搜索JWT的例子,JWT这个概念也只有蟋蟀和草稿。 Is this really so new and possibly a Google proprietary system?这真的很新而且可能是谷歌专有系统吗?

The java sample which is the closest I could manage to interpret looks pretty intensive and intimidating. java 样本是我能设法解释的最接近的样本,看起来非常密集和令人生畏。 There's got to be something out there in C# that I could at least start with.至少我可以从 C# 中找到一些东西。 Any help with this would be great!任何帮助都会很棒!

I found a base implementation of a Json Web Token and expanded on it with the Google flavor.我找到了 Json Web 令牌的基本实现,并使用 Google 风格对其进行了扩展。 I still haven't gotten it completely worked out but it's 97% there.我还没有完全解决它,但它已经完成了 97%。 This project lost it's steam, so hopefully this will help someone else get a good head-start:这个项目失去了动力,所以希望这能帮助其他人获得良好的开端:

Note: Changes I made to the base implementation (Can't remember where I found it,) are:注意:我对基本实现所做的更改(不记得我是在哪里找到的)是:

  1. Changed HS256 -> RS256更改 HS256 -> RS256
  2. Swapped the JWT and alg order in the header. Not sure who got it wrong, Google or the spec, but google takes it the way It is below according to their docs.在 header 中交换了 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;
    }
}

And then my google specific JWT class:然后是我的谷歌特定 JWT class:

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

After all these months have passed after the original question, it's now worth pointing out that Microsoft has devised a solution of their own.在最初的问题已经过去了几个月之后,现在值得指出的是,微软已经设计了自己的解决方案。 See 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 for details.请参阅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了解详情。

I've never used it but there is a JWT implementation on NuGet.我从未使用过它,但在 NuGet 上有一个 JWT 的实现。

Package: https://nuget.org/packages/JWT Package: https://nuget.org/packages/JWT

Source: https://github.com/johnsheehan/jwt来源: https://github.com/johnsheehan/jwt

.NET 4.0 compatible: https://www.nuget.org/packages/jose-jwt/ .NET 4.0 兼容: https://www.nuget.org/packages/jose-jwt/

You can also go here: https://jwt.io/ and click "libraries".您也可以在这里 go: https://jwt.io/并单击“库”。

Here is a working example:这是一个工作示例:

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

It took quite some time to collect the pieces scattered over the web, the docs are rather incomplete...收集散落在网络上的碎片花了相当长的时间,文档相当不完整......

This is my implementation of (Google) JWT Validation in .NET. It is based on other implementations on Stack Overflow and GitHub gists.这是我在 .NET 中对 (Google) JWT Validation 的实现。它基于 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;
        }
    }
}

Note that, in order to use it, you need to add a reference to the NuGet package System.Net.Http.Formatting.Extension .请注意,为了使用它,您需要添加对 NuGet package System.Net.Http.Formatting.Extension的引用。 Without this, the compiler will not recognize the ReadAsAsync<> method.否则,编译器将无法识别ReadAsAsync<>方法。

It would be better to use standard and famous libraries instead of writing the code from scratch.最好使用标准和著名的库,而不是从头开始编写代码。

  1. JWT for encoding and decoding JWT tokens JWT用于编码和解码 JWT 个令牌
  2. Bouncy Castle supports encryption and decryption, especially RS256 get it here Bouncy Castle支持加解密,特别是RS256 get it here

Using these libraries you can generate a JWT token and sign it using RS256 as below.使用这些库,您可以生成 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: the RSA private key should have the following format: ps:RSA私钥应该有如下格式:

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

Here is another REST-only working example for Google Service Accounts accessing G Suite Users and Groups, authenticating through JWT .这是访问G Suite用户和组的 Google 服务帐户的另一个REST-only工作示例,通过JWT进行身份验证。 This was only possible through reflection of Google libraries, since Google documentation of these APIs are beyond terrible .这只有通过谷歌库的反射才有可能,因为这些 API 的谷歌文档非常糟糕 Anyone used to code in MS technologies will have a hard time figuring out how everything goes together in Google services.任何习惯于使用 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;

Here is the list of classes and functions:这是类和函数的列表:

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)

Using System.Security.Cryptography.RSA I've modified John Sheehan's JWT library code that was expanded by @Levitikon to use RS256 , RSASSA-PKCS1-v1_5 with the SHA-256 hash algorithm使用System.Security.Cryptography.RSA我修改了John Sheehan 的 JWT 库代码,该代码由@Levitikon 扩展为使用RS256RSASSA-PKCS1-v1_5 with the SHA-256 hash algorithm

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

/// <summary>
/// adapted from<br/>
/// https://github.com/jwt-dotnet/jwt<br/>
/// https://stackoverflow.com/questions/10055158/is-there-any-json-web-token-jwt-example-in-c<br/>
/// https://stackoverflow.com/a/10106800/6620171<br/>
/// <br/>
/// JSON Web Token (JWT) is a compact, URL-safe means of representing<br/>
/// claims to be transferred between two parties.  The claims in a JWT<br/>
/// are encoded as a JSON object that is used as the payload of a JSON<br/>
/// Web Signature (JWS) structure or as the plaintext of a JSON Web<br/>
/// Encryption(JWE) structure, enabling the claims to be digitally<br/>
/// signed or integrity protected with a Message Authentication Code<br/>
/// (MAC) and/or encrypted.<br/>
/// <br/>
/// https://www.rfc-editor.org/rfc/rfc7519
/// </summary>
internal class JsonWebToken
{
    /// <summary>
    /// JWS uses cryptographic algorithms to digitally sign or create a MAC<br/>
    /// of the contents of the JWS Protected Header and the JWS Payload.<br/>
    /// <br/>
    /// https://www.rfc-editor.org/rfc/rfc7518#section-3
    /// </summary>
    public enum JwsAlgorythm
    {
        /// <summary>
        /// RSASSA-PKCS1-v1_5 using SHA-256<br/>
        /// This section defines the use of the RSASSA-PKCS1-v1_5 digital<br/>
        /// signature algorithm as defined in Section 8.2 of RFC 3447 [RFC3447]<br/>
        /// (commonly known as PKCS #1), using SHA-2 [SHS] hash functions.<br/>
        /// The RSASSA-PKCS1-v1_5 SHA-256 digital signature is generated as<br/>
        /// follows: generate a digital signature of the JWS Signing Input using<br/>
        /// RSASSA-PKCS1-v1_5-SIGN and the SHA-256 hash function with the desired<br/>
        /// private key.  This is the JWS Signature value.<br/>
        /// <br/>
        /// https://www.rfc-editor.org/rfc/rfc7518#section-3.3<br/>
        /// https://www.rfc-editor.org/rfc/rfc3447#section-8.2<br/>
        /// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
        /// </summary>
        RS256,
        /// <summary>
        /// No digital signature or MAC performed<br/>
        /// JWSs MAY also be created that do not provide integrity protection.<br/>
        /// Such a JWS is called an Unsecured JWS.  An Unsecured JWS uses the<br/>
        /// "alg" value "none" and is formatted identically to other JWSs, but<br/>
        /// MUST use the empty octet sequence as its JWS Signature value.<br/>
        /// Recipients MUST verify that the JWS Signature value is the empty<br/>
        /// octet sequence.<br/>
        /// <br/>
        /// https://www.rfc-editor.org/rfc/rfc7518#section-3.6<br/>
        /// https://www.rfc-editor.org/rfc/rfc7519#section-6
        /// </summary>
        none
    }
    public static string Encode(object payload, JwsAlgorythm algo, RSA rsa)
    {
        if (payload == null) { throw new ArgumentNullException("payload"); }
        if (algo != JwsAlgorythm.RS256 && algo != JwsAlgorythm.none) { throw new ArgumentException("Invalid JwsAlgorythm specified"); }
        if (rsa == null && algo == JwsAlgorythm.RS256) { throw new ArgumentNullException("Encoding of secured JWT requires an RSA object"); }
        List<string> segments = new List<string>();
        var header = new { typ = "JWT", alg = algo.ToString() };

        string strHeader = JsonConvert.SerializeObject(header, Formatting.None);
        string strPayload = JsonConvert.SerializeObject(payload, Formatting.None);
        byte[] headerBytes = Encoding.UTF8.GetBytes(strHeader);
        byte[] payloadBytes = Encoding.UTF8.GetBytes(strPayload);

        segments.Add(Base64UrlEncode(headerBytes));
        segments.Add(Base64UrlEncode(payloadBytes));

        if (algo == JwsAlgorythm.none)
        {
            segments.Add(string.Empty);
            return string.Join(".", segments.ToArray());
        }

        string stringToSign = string.Join(".", segments.ToArray());

        byte[] bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
        byte[] signature = rsa.SignData(bytesToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        segments.Add(Base64UrlEncode(signature));
        return string.Join(".", segments.ToArray());
    }
    public static Tuple<string, string> Decode(string token, JwsAlgorythm algo, bool verify, RSA rsa = null)
    {
        if (algo != JwsAlgorythm.RS256 && algo != JwsAlgorythm.none) { throw new ArgumentException("Invalid JwsAlgorythm specified"); }
        if (verify && rsa == null && algo == JwsAlgorythm.RS256) { throw new ArgumentNullException("Verification of secured JWT requires an RSA object"); }
        string[] parts = token.Split('.');
        string header = parts[0];
        string payload = parts[1];
        byte[] crypto = Base64UrlDecode(parts[2]);

        string headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
        JObject headerData = JObject.Parse(headerJson);
        string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
        JObject payloadData = JObject.Parse(payloadJson);

        if (verify)
        {
            if (algo == JwsAlgorythm.none)
            {
                if (crypto.Length != 0)
                {
                    throw new ApplicationException(string.Format("Invalid signature"));
                }
            }
            else if (algo == JwsAlgorythm.RS256)
            {
                byte[] bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
                bool valid = rsa.VerifyData(bytesToSign, crypto, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                if (!valid)
                {
                    throw new ApplicationException(string.Format("Invalid signature"));
                }
            }
        }
        return new Tuple<string, string>(headerData.ToString(), payloadData.ToString());
    }

    // from JWT spec
    private static string Base64UrlEncode(byte[] input)
    {
        string 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)
    {
        string 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 Exception("Invalid base64url string");
        }
        byte[] converted = Convert.FromBase64String(output); // Standard base64 decoder
        return converted;
    }
}

Usage:用法:

X509Certificate2 cert = new X509Certificate2("C:\\test\\keypair.pfx", "notasecret");

long secsSinceEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var jwt = new
{
    exp = secsSinceEpoch + 600,
    iss = "my client id",
    aud = "h++ps://webapi.com",
    sub = "my subscriber id",
    iat = secsSinceEpoch,
    nbf = secsSinceEpoch,
    jti = RandomString(21),
};

string jwtEncoded = JsonWebToken.Encode(jwt, JsonWebToken.JwsAlgorythm.RS256, cert.GetRSAPrivateKey());
Tuple<string, string> jwtDecoded = JsonWebToken.Decode(jwtEncoded, JsonWebToken.JwsAlgorythm.RS256, true, cert.GetRSAPublicKey());

Console.WriteLine(jwtDecoded);

Output: Output:

({
  "typ": "JWT",
  "alg": "RS256"
}, {
  "exp": 1668732075,
  "iss": "my client id",
  "aud": "h++ps://webapi.com",
  "sub": "my subscriber id",
  "iat": 1668731475,
  "nbf": 1668731475,
  "jti": "tCUpk2i5bNBVBcj7LzV5U"
})

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM