您现在的位置是: 首页 > .NET CORE > 文章详情 文章详情
aspnet core 2.1中使用jwt从原理到精通一
2019-08-19 【.NET CORE】 1707人浏览
原理
jwt对所有语言都是通用的,只要知道秘钥,另一一种语言有可以对jwt的有效性进行判断;
jwt的组成;Header部分Base64转化.Payload部分Base64转化.使用HS256方式根据秘钥对前面两部分进行加密后再Base64转化,其中使用的hs256加密是header部分指定的,也可以通过官网的查看,如下图:
原理就这么简单,那究竟用怎样使用C#来实现呢,又怎么确定它的正确性呢?,请继续
使用C#实现
我们定义一个今天方法,其中需要使用到Microsoft.IdentityModel.Tokens.dll,asp.net core 2.1再带,如果其他版本,没有自带,需要nuget 一下这个类库
CreateToken(Dictionary payLoad, expiresMinute, Dictionary header = (header == = Dictionary( List> KeyValuePair(, KeyValuePair(, now =] = ToUnixEpochDate( now); payLoad[] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute))); encodedHeader = encodedPayload = hs256 = encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(.Concat(encodedHeader, encodedJwt = .Concat(encodedHeader, , encodedPayload,
public static long ToUnixEpochDate(DateTime date) =>
(long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
该方法很简单,只需要传入header键值对和payLoad键值对,然后根据原理进行Base64转换和hs256加密,接下来我们来使用一个测试类对其进行测试,代码如下:
[TestMethod] public void TokenValidateTest() { DictionarypayLoad = new Dictionary (); payLoad.Add("sub", "rober"); payLoad.Add("jti", "09e572c7-62d0-4198-9cce-0915d7493806"); payLoad.Add("nbf", null); payLoad.Add("exp", null); payLoad.Add("iss", "roberIssuer"); payLoad.Add("aud", "roberAudience"); payLoad.Add("age", 30); var encodeJwt = TokenContext.CreateToken(payLoad, 30); var result = TokenContext.Validate(encodeJwt, (load) => { return true; }); Assert.IsTrue(result); }
先不管后面的验证,我们先看看其中生成的encodeJwt的值:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyb2JlciIsImp0aSI6IjY0OWMyYjUxLTE4ZGQtNDEzYy05Yzc5LTI4NWNhMDAxODU2NSIsIm5iZiI6MTU0MDYxMDY2NSwiZXhwIjoxNTQwNjEyNDY1LCJpc3MiOiJyb2Jlcklzc3VlciIsImF1ZCI6InJvYmVyQXVkaWVuY2UiLCJhZ2UiOjMwfQ.7Is2KYHAtSr5fW2gPU1jGeHPzz2ULCZJGcWb40LSYyw
第一部分和第二部分,并不是加密,只是Base64转换,我们可以通过其他语言轻松转换回来,如下使用javascript进行转,window.atob(base64加密) window.btoa(base64解密)
var header=JSON.parse(window.atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'))
如下图:
我再对payLoa进行转换回来, var payLoad=JSON.parse(window.atob('eyJzdWIiOiJyb2JlciIsImp0aSI6IjY0OWMyYjUxLTE4ZGQtNDEzYy05Yzc5LTI4NWNhMDAxODU2NSIsIm5iZiI6MTU0MDYxMDY2NSwiZXhwIjoxNTQwNjEyNDY1LCJpc3MiOiJyb2Jlcklzc3VlciIsImF1ZCI6InJvYmVyQXVkaWVuY2UiLCJhZ2UiOjMwfQ')) ,如下图:
所以,从这里可以看出来,Base64并不是属于加密,只是简单转换,因此,不能在payLoad中存放重要内容,比如密码等
使用aspnetcore 中自带的类生成jwt
aspnet core中自带了一个jwt帮助类,其实原理一样,对上面做了封装,丰富了一个内容,我们继续使用一个静态方法,如下
////// 创建jwtToken,采用微软内部方法,默认使用HS256加密,如果需要其他加密方式,请更改源码 /// 返回的结果和CreateToken一样 /// /// /// 有效分钟 ///public static string CreateTokenByHandler(Dictionary payLoad, int expiresMinute) { 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 (); 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.FromMinutes(expiresMinute)), signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256)); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); return encodedJwt; }
它效果和上面一模一样,如果使用同样的header 、payload、秘钥,生成的jwt肯定一样,这里就不演示了,感兴趣的可以自行尝试;
aspnetcore中如何使用自定义jwt验证
上面讲了那么多,只是为了大家更好的理解如何使用jwt进行验证,那是jwt是如何进行验证的呢?,如果一个http请求过来,一般jwt携带在http请求头部的Authorization中;先不看如何获取,先看看他是如何验证的,我们再定义个静态方法,如下:
////// 验证身份 验证签名的有效性, /// /// /// 自定义各类验证; 是否包含那种申明,或者申明的值, /// 例如:payLoad["aud"]?.ToString() == "roberAuddience"; /// 例如:验证是否过期 等 ///public static bool Validate(string encodeJwt,Func ,bool> validatePayLoad) { var success = true; var jwtArr = encodeJwt.Split('.'); var header = JsonConvert.DeserializeObject >(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject >(Base64UrlEncoder.Decode(jwtArr[1])); var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey)); //首先验证签名是否正确(必须的) 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
其中 validatePayLoad 参数是一个自定义的验证的Fun,执行该Fun方法时会把解密后的payload作为参数传入进去
我们验证通过分为两部分,
第一,必须的(自认为的)
jwt签名是否正确,请看以上代码实现
jwt是否在可以时间内,请看以上代码实现
第二,自定义的(各复杂的,原理就是获取payLoad 的某个值,然后对这个值进行各种判读--等于,大于,包含,)
该jwt是不是进入黑名单
aud==‘roberAudience’
我们来通过一个测试类验证
[TestMethod] public void TokenCustomerValidateTest() { DictionarypayLoad = new Dictionary (); payLoad.Add("sub", "rober"); payLoad.Add("jti", Guid.NewGuid().ToString()); payLoad.Add("nbf", null); payLoad.Add("exp", null); payLoad.Add("iss", "roberIssuer"); payLoad.Add("aud", "roberAudience"); payLoad.Add("age", 30); var encodeJwt = TokenContext.CreateToken(payLoad, 30); var result = TokenContext.Validate(encodeJwt, (load) => { var success = true; //验证是否包含aud 并等于 roberAudience success = success&& load["aud"]?.ToString() == "roberAudience"; //验证age>20等 int.TryParse(load["age"].ToString(), out int age); Assert.IsTrue(age > 30); //其他验证 jwt的标识 jti是否加入黑名单等 return success; }); Assert.IsTrue(result); }
如上面,我们可以把jwt中的payload解析出来,然后进行各种复杂的想要的验证;
其实,aspnet core中的基于角色,用户、策略,自定义策略的验证就相当这里的自定义验证,一下章将详细说明和对比,这里暂时不讲解
看完上面,是不是觉得jwt很简单就,主要就两部
创建jwt;
验证jwt;
完整代码如下:
////// Token上下文,负责token的创建和验证 /// public class TokenContext { ////// 秘钥,可以从配置文件中获取 /// public static string securityKey = "GQDstclechengroberbojPOXOYg5MbeJ1XT0uFiwDVvVBrk"; ////// 创建jwttoken,源码自定义 /// /// /// ///public static string CreateToken(Dictionary payLoad,int expiresMinute, Dictionary header = null) { if (header == null) { header = new Dictionary (new List >() { new KeyValuePair ("alg", "HS256"), new KeyValuePair ("typ", "JWT") }); } //添加jwt可用时间(应该必须要的) var now = DateTime.UtcNow; payLoad["nbf"] = ToUnixEpochDate( now);//可用时间起始 payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用时间结束 var encodedHeader = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(header)); var encodedPayload = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(payLoad)); var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey)); var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload)))); var encodedJwt = string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature); return encodedJwt; } /// /// 创建jwtToken,采用微软内部方法,默认使用HS256加密,如果需要其他加密方式,请更改源码 /// 返回的结果和CreateToken一样 /// /// /// 有效分钟 ///public static string CreateTokenByHandler(Dictionary payLoad, int expiresMinute) { 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 (); 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.FromMinutes(expiresMinute)), signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256)); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); return encodedJwt; } /// /// 验证身份 验证签名的有效性, /// /// /// 自定义各类验证; 是否包含那种申明,或者申明的值, /// 例如:payLoad["aud"]?.ToString() == "roberAuddience"; /// 例如:验证是否过期 等 ///public static bool Validate(string encodeJwt,Func ,bool> validatePayLoad) { var success = true; var jwtArr = encodeJwt.Split('.'); var header = JsonConvert.DeserializeObject >(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject >(Base64UrlEncoder.Decode(jwtArr[1])); var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey)); //首先验证签名是否正确(必须的) 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 /// 获取jwt中的payLoad /// /// /// public static Dictionary GetPayLoad(string encodeJwt) { var jwtArr = encodeJwt.Split('.'); var payLoad = JsonConvert.DeserializeObject >(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); } 以上就是jwt的基本内容,它确实很简单,不要被aspnet core中的各种写法给搞晕了,只要是jwt相关的验证都是基于上面这些东西
很赞哦! (0)
相关文章
热门收藏
- .net core Redis分布式缓存的客户端实现
- .net core API 统一拦截错误
- ASP.NET Core MVC中的IActionFilter.OnActionExecuted方法执行时,Controller中Action返回的对象是否已经输出到Http Response中
- aspnet core 2.1中使用jwt从原理到精通一
- aspnet core 2.1中使用jwt从原理到精通二
- aspnet core 2.1中使用jwt从原理到精通三
- ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统
- 消除 ASP.NET Core 告警 "No XML encryptor configured. Key may be persisted to storage in unencrypted form
- .NET Core 中基于 IHostedService 实现后台定时任务
- 【.Net Core】获取绝对路径、相对路径