我一直在使用JWT库来解码Json Web Token,并希望切换到Microsoft的官方JWT实现System.IdentityModel.Tokens.Jwt。
文档非常稀疏,所以我很难弄清楚如何完成我一直在用 JWT 库做的事情。 使用 JWT 库,有一个解码方法,该方法采用 base64 编码的 JWT 并将其转换为 JSON,然后可以反序列化。我想使用System.IdentityModel.Tokens.Jwt做类似的事情,但经过相当多的挖掘,无法弄清楚如何。
对于它的价值,我正在从cookie中读取JWT令牌,以便与Google的身份框架一起使用。
任何帮助将不胜感激。
在包中,有一个名为 JwtSecurityTokenHandler
的类,它派生自 System.IdentityModel.Tokens.SecurityTokenHandler
。在 WIF 中,这是用于反序列化和序列化安全令牌的核心类。
该类有一个 ReadToken(String)
方法,该方法将采用 base64 编码的 JWT 字符串并返回表示 JWT 的SecurityToken
。
该SecurityTokenHandler
还有一个ValidateToken(SecurityToken)
方法,可以获取您的SecurityToken
并创建一个ReadOnlyCollection<ClaimsIdentity>
。通常对于 JWT,这将包含一个 ClaimsIdentity
对象,该对象具有一组表示原始 JWT 属性的声明。
JwtSecurityTokenHandler
为ValidateToken
定义了一些额外的重载,特别是它有一个ClaimsPrincipal ValidateToken(JwtSecurityToken, TokenValidationParameters)
重载。TokenValidationParameters
参数允许您指定令牌签名证书(作为X509SecurityTokens
的列表)。它还具有将JWT视为string
而不是SecurityToken
的重载。
执行此操作的代码相当复杂,但可以在开发人员示例中名为"ADAL - 本机应用程序到 REST 服务 - 通过浏览器对话框使用 ACS 进行身份验证"的开发人员示例中的 Global.asax.cx 代码(TokenValidationHandler
类)中找到,位于
http://code.msdn.microsoft.com/AAL-Native-App-to-REST-de57f2cc
或者,JwtSecurityToken
类具有不在基SecurityToken
类上的其他方法,例如获取所包含声明而不通过 ClaimsIdentity
集合的 Claims
属性。它还具有一个 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
也许,一个可以满足您的需求或至少可以适应它们。
我只是想知道为什么要使用一些库进行 JWT 令牌解码和验证。
可以使用以下伪代码创建编码的 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 urlsafe编码/解码,请参阅另一个SO问题,以及wiki和RFC
我在 System.IdentityModel.Tokens
和 System.IdentityModel.Tokens.Jwt
之间遇到了版本问题,这是 Jwt 版本 5.0.0.0 之后的已知问题。因此,相反,我下载了最新版本的Microsoft.IdentityModel.Tokens
- 注意Microsoft - 并且一切正常。这是我制作的一个很好的片段,用于验证和解码自定义生成的 JWT 令牌并解析其 JSON 内容。
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