一、什么JSON Web Tokens?
JSON Web Tokens是一种开放的行业标准 RFC 7519方法,用于在双方之间安全地表示索赔。
JWT.IO允许您解码,验证和生成JWT。其中。JWT 支持任何语言的解码。
官网地址:https://jwt.io/
二、JWT 的结构
JWT由三部分组成:
1.Header :头信息
标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法
如:
{ "alg": "HS256", "typ": "JWT"}
然后,这个JSON被编码为Base64Url,形成JWT的第一部分。
2.Payload :有效载荷
令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的声明。
示例有效负载可以是:
{ "sub": "1234567890", "name": "John Doe", "admin": true}
然后,有效负载经过Base64Url编码,形成JSON Web令牌的第二部分。由于JWT 支持任何语言的解码,所以不要在Payload 中放置重要信息
3.Signature :签名
如:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
签名由三部分组成,base64编码后的表头、base64编码后的有效载荷和秘钥,三者中间由 '.' 字符连接。这里举例的是采用HMACSHA256算法加密的。
其中secret秘钥是注册后创建app之后生成的(此秘钥非常重要,不要告诉别人哟)。
三、代码实现(以net core 为例):
1.首先引用 Microsoft.IdentityModel.Tokens.dll。net core 2.1版本中内置了这个库,其他版本要引用。
2.为了方便,我把整个jwt结构写了配置,并在项目中引用。
创建了对应的配置节点实体:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace fwt.Models { /// <summary> /// jwt /// </summary> public class JWTTokenModel { public JWTHeader header { get; set; } public JWTPayLoad payLoad { get; set; } public string secret { get; set; } } /// <summary> /// header /// </summary> public class JWTHeader { public string alg { get; set; } public string typ { get; set; } } /// <summary> /// payLoad /// </summary> public class JWTPayLoad { public string iss { get; set; } public string aud { get; set; } public string age { get; set; } public string sub { get; set; } public long nbf { get; set; } public long exp { get; set; } } }
通过构造函数依赖注入,在Controller中获取配置信息
Startup 中:
services.Configure<JWTTokenModel>(Configuration.GetSection("JWTSetting"));
然后编写jwt帮助类,分别创建jwttoken和验证jwttoken
using fwt.Models; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace fwt { /// <summary> /// token 帮助类 /// </summary> public static class TokenContext { /// <summary> /// 创建jwttoken,源码自定义 /// </summary> public static string CreateToken(JWTPayLoad payLoad, string secret, JWTHeader header = null) { if (header == null) { header = new JWTHeader() { alg = "HS256", typ = "JWT" }; } //添加jwt可用时间(应该必须要的) var now = DateTime.UtcNow; payLoad.nbf = ToUnixEpochDate(now);//可用时间起始 payLoad.exp = ToUnixEpochDate(now.Add(TimeSpan.FromSeconds(3600)));//可用时间结束,其中3600是有效时间,根据实际情况自定义 var encodedHeader = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(header)); var encodedPayload = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(payLoad)); var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secret)); var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload)))); var encodedJwt = string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature); return encodedJwt; } /// <summary> /// 创建jwtToken,采用微软内部方法,默认使用HS256加密,如果需要其他加密方式,请更改源码 /// 返回的结果和CreateToken一样 /// </summary> public static string CreateTokenByHandler(Dictionary<string, object> payLoad, string secret) { var now = DateTime.UtcNow; // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims. // You can add other claims here, if you want: var claims = new List<Claim>(); foreach (var key in payLoad.Keys) { var tempClaim = new Claim(key, payLoad[key]?.ToString()); claims.Add(tempClaim); } // Create the JWT and write it to a string var jwt = new JwtSecurityToken( issuer: null, audience: null, claims: claims, notBefore: now, expires: now.Add(TimeSpan.FromSeconds(3600)), signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret)), SecurityAlgorithms.HmacSha256)); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); return encodedJwt; } /// <summary> /// 验证身份 验证签名的有效性, /// </summary> /// <param name="encodeJwt"></param> /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值, </param> /// 例如:payLoad["aud"]?.ToString() == "roberAuddience"; /// 例如:验证是否过期 等 public static bool Validate(string encodeJwt, Func<Dictionary<string, object>, bool> validatePayLoad,string secret) { var success = true; var jwtArr = encodeJwt.Split('.'); var header = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1])); var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secret)); //首先验证签名是否正确(必须的) success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))); if (!success) { return success;//签名不正确直接返回 } //其次验证是否在有效期内(也应该必须) var now = ToUnixEpochDate(DateTime.UtcNow); success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())); //再其次 进行自定义的验证 success = success && validatePayLoad(payLoad); return success; } /// <summary> /// 获取jwt中的payLoad /// </summary> /// <param name="encodeJwt"></param> /// <returns></returns> public static Dictionary<string, object> GetPayLoad(string encodeJwt) { var jwtArr = encodeJwt.Split('.'); var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1])); return payLoad; } public static long ToUnixEpochDate(DateTime date) => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds); } }
控制器调用
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using fwt.Models; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using System.Text; using System.Security.Cryptography; using Microsoft.Extensions.Options; namespace fwt.Controllers { public class HomeController : Controller { private readonly JWTTokenModel _jWTTokenModel; //依赖注入 public HomeController(IOptions<JWTTokenModel> jWTTokenModel) { _jWTTokenModel = jWTTokenModel.Value; } public IActionResult Index() { //获取token string tokenstr = TokenContext.CreateToken(_jWTTokenModel.payLoad, _jWTTokenModel.secret, _jWTTokenModel.header); //自定义验证 Func<Dictionary<string, object>, bool> func = delegate (Dictionary<string, object> s) { if (s["iss"].ToString() == _jWTTokenModel.payLoad.iss && s["aud"].ToString() == _jWTTokenModel.payLoad.aud && s["sub"].ToString() == _jWTTokenModel.payLoad.sub) { return true; } return false; }; //验证token bool success = TokenContext.Validate(tokenstr, func, _jWTTokenModel.secret); return View(); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } }
ok,原理差不多就是这个样子。官网中也提供了解码的调试器,可以解码生成的token
将生成的token复制到左边即可。
使用场景:比如webapi接口的token验证,就可以用到jwt,先获取登录后的token,把token传入authorization中才能进行下面接口的调用。
总结一哈:由于JSON比XML更简洁,因此在编码时它的大小也更小,使得JWT比SAML更紧凑。这使得JWT成为在HTML和HTTP环境中传递的不错选择。
JSON解析器在大多数编程语言中很常见,因为它们直接映射到对象。相反,XML没有自然的文档到对象映射。这使得使用JWT比使用SAML断言更容易。
个人理解:jwt其实是一种规范,一种加密验证的解决方案,更多的其实是给一个思路,一个标准,不一定要完全按照jwt的格式去加密。一定程度上
可以取代session和cookie进行用户认证,两者各有优缺点。