【问题标题】:Decoding and verifying JWT token using System.IdentityModel.Tokens.Jwt使用 System.IdentityModel.Tokens.Jwt 解码和验证 JWT 令牌
【发布时间】:2013-09-11 17:42:57
【问题描述】:

我一直在使用 JWT 库来解码 Json Web Token,并想切换到 Microsoft 的官方 JWT 实现 System.IdentityModel.Tokens.Jwt

文档非常稀少,所以我很难弄清楚如何完成我一直在使用 JWT 库所做的事情。使用 JWT 库,有一个 Decode 方法,它采用 base64 编码的 JWT 并将其转换为 JSON,然后可以反序列化。我想使用 System.IdentityModel.Tokens.Jwt 做类似的事情,但经过大量挖掘,无法弄清楚如何。

不管怎样,我正在从 cookie 中读取 JWT 令牌,用于 Google 的身份框架。

任何帮助将不胜感激。

【问题讨论】:

标签: .net wif jwt


【解决方案1】:

我在System.IdentityModel.TokensSystem.IdentityModel.Tokens.Jwt 之间遇到了版本问题,这是 Jwt 5.0.0.0 版本之后的一个已知问题。所以,我下载了最新版本的Microsoft.IdentityModel.Tokens - 注意Microsoft,一切正常。这是我为验证和解码自定义生成的 JWT 令牌并解析其 JSON 内容而制作的一个不错的 sn-p。

using System.Collections.Generic;
using System.Linq;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

public static void Main()
{
    var key = "qwertyuiopasdfghjklzxcvbnm123456";
    var securityKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(key));

    string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NDA0MDY1MjIsImV4cCI6MTY3MTk0MjUyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsImZvbyI6ImJhciJ9.QqcxZWEUt5YLraLRg5550Ls7aMVqm7aCUcbU7uB1qgY";

    TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
    {
        IssuerSigningKey = securityKey,
        RequireExpirationTime = true,
        ValidateLifetime = true,
        ValidateAudience = true,
        ValidateIssuer = true,
        ValidIssuer = "Online JWT Builder",
        ValidAudience = "www.example.com",
    };

    if (ValidateToken(token, tokenValidationParameters))
    {
        var TokenInfo = new Dictionary<string, string>();
        var handler = new JwtSecurityTokenHandler();
        var jwtSecurityToken = handler.ReadJwtToken(token);
        var claims = jwtSecurityToken.Claims.ToList();

        foreach (var claim in claims)
        {
            TokenInfo.Add(claim.Type, claim.Value);
        }

        string sub = jwtSecurityToken.Subject;
        string iss = jwtSecurityToken.Issuer;
        DateTime iat = jwtSecurityToken.IssuedAt;
        List<string> audiences = new List<string>(jwtSecurityToken.Audiences);
        DateTime exp = jwtSecurityToken.ValidTo;
        string bar;
        bool ifBar = TokenInfo.TryGetValue("foo", out bar);
        Console.WriteLine("Subject: " + sub);
        Console.WriteLine("Issuer: " + iss);
        Console.WriteLine("Issued At: " + iat);
        foreach (var member in audiences)
        {
            Console.WriteLine("Audience: " + member);
        }
        Console.WriteLine("Expiration: " + exp);
        Console.WriteLine("foo: " + bar);
    }
    Console.ReadLine();
}

private static bool ValidateToken(string token, TokenValidationParameters tvp)
{
    try
    {
        var handler = new JwtSecurityTokenHandler();
        SecurityToken securityToken;
        ClaimsPrincipal principal = handler.ValidateToken(token, tvp, out securityToken);
        return true;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return false;
    }
}

输出

Subject: jrocket@example.com
Issuer: Online JWT Builder
Issued At: 12/25/2022 4:28:42 AM
Audience: www.example.com
Expiration: 12/25/2022 4:28:42 AM
foo: bar

【讨论】:

    【解决方案2】:

    我只是想知道为什么要使用一些库来进行 JWT 令牌解码和验证。

    可以使用following pseudocode创建编码的JWT令牌

    var headers = base64URLencode(myHeaders);
    var claims = base64URLencode(myClaims);
    var payload = header + "." + claims;
    
    var signature = base64URLencode(HMACSHA256(payload, secret));
    
    var encodedJWT = payload + "." + signature;
    

    没有任何特定的库很容易做到。使用以下代码:

    using System;
    using System.Text;
    using System.Security.Cryptography;
    
    public class Program
    {   
        // More info: https://stormpath.com/blog/jwt-the-right-way/
        public static void Main()
        {           
            var header = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
            var claims = "{\"sub\":\"1047986\",\"email\":\"jon.doe@eexample.com\",\"given_name\":\"John\",\"family_name\":\"Doe\",\"primarysid\":\"b521a2af99bfdc65e04010ac1d046ff5\",\"iss\":\"http://example.com\",\"aud\":\"myapp\",\"exp\":1460555281,\"nbf\":1457963281}";
    
            var b64header = Convert.ToBase64String(Encoding.UTF8.GetBytes(header))
                .Replace('+', '-')
                .Replace('/', '_')
                .Replace("=", "");
            var b64claims = Convert.ToBase64String(Encoding.UTF8.GetBytes(claims))
                .Replace('+', '-')
                .Replace('/', '_')
                .Replace("=", "");
    
            var payload = b64header + "." + b64claims;
            Console.WriteLine("JWT without sig:    " + payload);
    
            byte[] key = Convert.FromBase64String("mPorwQB8kMDNQeeYO35KOrMMFn6rFVmbIohBphJPnp4=");
            byte[] message = Encoding.UTF8.GetBytes(payload);
    
            string sig = Convert.ToBase64String(HashHMAC(key, message))
                .Replace('+', '-')
                .Replace('/', '_')
                .Replace("=", "");
    
            Console.WriteLine("JWT with signature: " + payload + "." + sig);        
        }
    
        private static byte[] HashHMAC(byte[] key, byte[] message)
        {
            var hash = new HMACSHA256(key);
            return hash.ComputeHash(message);
        }
    }
    

    令牌解码是上面代码的反转版本。要验证签名,您需要将签名部分与计算的签名进行比较。

    更新:对于那些如何努力进行 base64 url​​safe 编码/解码的人,请参阅另一个 SO question,以及 wiki 和 RFCs

    【讨论】:

    • 不错的答案。尽管由于您在此处显示基于 HMAC 的签名,因此了解实现 HMAC 验证的库中的一些关键漏洞可能是有意义的,详见此处的 Auth0 站点:auth0.com/blog/2015/03/31/…
    • 我觉得这是最好的答案。 OP 要求提供有关 JWT 的具体信息,本文通过一个明确的示例解决了这些信息。
    • 这个答案解释并演示了如何en对 JWT 进行编码,当问题非常清楚地涉及 de 编码时。这可能是一个不错的答案,但它是对一个完全不同的问题的答案。
    • “答案”的概念是解决一个问题,而不是通过期望某人解决某种逆向难题来提出难题。床边,知道如何编码 一定意味着您也知道如何解码,因为这可能还涉及处理 3rd 方令牌和检索密钥以验证其签名,而不是简单地使用密钥签署你自己的。在任何情况下,一个真正回答定义的问题的答案不是相比是“更好”的答案>确实,这是我正在回应的观察结果。
    • 除非您真的知道自己在做什么,否则不要自己编写安全相关部分的代码。总有一些事情你很容易做错。存在易受攻击的 jwt 库这一事实证明,除非您真的认为自己比这些库的作者更了解 JWT,否则自己编写代码是一个坏主意。但是,这个答案对于了解 JWT 仍然很有用。
    【解决方案3】:

    在包中有一个名为JwtSecurityTokenHandler 的类,它派生自System.IdentityModel.Tokens.SecurityTokenHandler。在 WIF 中,这是反序列化和序列化安全令牌的核心类。

    该类有一个 ReadToken(String) 方法,该方法将采用 base64 编码的 JWT 字符串并返回一个代表 JWT 的 SecurityToken

    SecurityTokenHandler 也有一个ValidateToken(SecurityToken) 方法,它接受你的SecurityToken 并创建一个ReadOnlyCollection&lt;ClaimsIdentity&gt;。通常对于 JWT,这将包含一个 ClaimsIdentity 对象,该对象具有一组表示原始 JWT 属性的声明。

    JwtSecurityTokenHandlerValidateToken 定义了一些额外的重载,特别是,它有一个ClaimsPrincipal ValidateToken(JwtSecurityToken, TokenValidationParameters) 重载。 TokenValidationParameters 参数允许您指定令牌签名证书(作为X509SecurityTokens 的列表)。它还有一个重载,将 JWT 作为 string 而不是 SecurityToken

    执行此操作的代码相当复杂,但可以在名为“ADAL - Native App to REST service - Authentication with ACS via Browser Dialog”的开发人员示例中的 Global.asax.cx 代码(TokenValidationHandler 类)中找到",位于

    http://code.msdn.microsoft.com/AAL-Native-App-to-REST-de57f2cc

    或者,JwtSecurityToken 类具有其他方法,这些方法不在基础 SecurityToken 类上,例如 Claims 属性无需通过 ClaimsIdentity 集合即可获取包含的声明。它还有一个Payload 属性,它返回一个JwtPayload 对象,让您可以获取令牌的原始JSON。这取决于您的方案最合适的方法。

    SecurityTokenHandler 类的一般(即非 JWT 特定)文档位于

    http://msdn.microsoft.com/en-us/library/system.identitymodel.tokens.securitytokenhandler.aspx

    根据您的应用程序,您可以将 JWT 处理程序配置到 WIF 管道中,就像任何其他处理程序一样。

    在不同类型的应用中使用了 3 个示例

    http://code.msdn.microsoft.com/site/search?f%5B0%5D.Type=SearchText&f%5B0%5D.Value=aal&f%5B1%5D.Type=User&f%5B1%5D.Value=Azure%20AD%20Developer%20Experience%20Team&f%5B1%5D.Text=Azure%20AD%20Developer%20Experience%20Team

    也许,有一个可以满足您的需求,或者至少可以适应它们。

    【讨论】:

    • 非常感谢您的回答。那么,一旦我有了 ClaimsIdentity,我该如何对照公钥来验证它呢?具体来说,我正在尝试根据他们的公钥 (gstatic.com/authtoolkit/cert/gitkit_cert.pem) 验证 google 身份工具包 JWT
    • 更新了我的答案 - 我无法提供完整的源代码,但我为您指出了适当的开发人员示例的方向。希望对您有所帮助。
    • @w.brian - 我也在尝试这样做。我有一个可以解码的令牌和一个我想验证的公钥,但即使查看这些样本,我也很难了解我是如何做到这一点的。您对哪些代码实际上对您有帮助有任何指示吗?谢谢。
    • ValidateToken遭遇2038问题
    猜你喜欢
    • 1970-01-01
    • 2019-06-02
    • 2018-05-18
    • 2017-02-17
    • 2020-11-02
    • 2018-08-01
    • 2019-06-23
    • 1970-01-01
    • 2019-10-08
    相关资源
    最近更新 更多