首页 > 网络编程 > ASP.NET > 正文

asp net core 2.1中如何使用jwt(从原理到精通)_实用技巧

2018-11-10 10:00:31

为什么使用 Jwt

最近,移动开发的劲头越来越足,学校搞的各种比赛都需要用手机 APP 来撑场面,所以,作为写后端的,很有必要改进一下以往的基于 Session 的身份认证方式了,理由如下:

  • 移动端经常要保持长时间(1 到 2 星期)在线,但是 Session 却不好在服务端保存这么久,虽然可以持久化到数据库,但是还是挺费资源
  • 移动端往往不是使用的网页技术,所以藏在 Cookie 里面的 SessionId 不是很方便的传递给服务端
  • 服务端暴露给客户端的接口往往是 RESTful 风格的,这是一种无状态的 API 风格,所以身份认证的方式最好也是无状态的才好

所以我选择了使用 Jwt (Json Web Token) 这个技术。Jwt 是一种无状态的分布式的身份验证方式,与 Session 相反,Jwt 将用户信息存放在 Token 的 payload 字段保存在客户端,通过 RSA 加密的方式,保证数据不会被篡改,验证数据有效性。

下面是一个使用 Jwt 的系统的身份验证流程:

可以看出,用户的信息保存在 Token 中,而 Token 分布在用户的设备中,所以服务端不再需要在内存中保存用户信息了
身份认证的 Token 传递时以一种相当简单的格式保存在 header 中,方便客户端对其进行操作

原理

jwt对所有语言都是通用的,只要知道秘钥,另一一种语言有可以对jwt的有效性进行判断;

jwt的组成;Header部分Base64转化.Payload部分Base64转化.使用HS256方式根据秘钥对前面两部分进行加密后再Base64转化,其中使用的hs256加密是header部分指定的,也可以通过官网的查看,如下图:

原理就这么简单,那究竟用怎样使用C#来实现呢,又怎么确定它的正确性呢?,请继续

使用C#实现

我们定义一个今天方法,其中需要使用到Microsoft.IdentityModel.Tokens.dll,asp.net core 2.1再带,如果其他版本,没有自带,需要nuget 一下这个类库

/// <summary> /// 创建jwttoken,源码自定义 /// </summary> /// <param name="payLoad"></param> /// <param name="header"></param> /// <returns></returns> public static string CreateToken(Dictionary<string, object> payLoad,int expiresMinute, Dictionary<string, object> header = null) {  if (header == null)  {  header = new Dictionary<string, object>(new List<KeyValuePair<string, object>>() {   new KeyValuePair<string, object>("alg", "HS256"),   new KeyValuePair<string, object>("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; } 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() {  Dictionary<string, object> payLoad = new Dictionary<string, object>();  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帮助类,其实原理一样,对上面做了封装,丰富了一个内容,我们继续使用一个静态方法,如下

/// <summary> /// 创建jwtToken,采用微软内部方法,默认使用HS256加密,如果需要其他加密方式,请更改源码 /// 返回的结果和CreateToken一样 /// </summary> /// <param name="payLoad"></param> /// <param name="expiresMinute">有效分钟</param> /// <returns></returns> public static string CreateTokenByHandler(Dictionary<string, object> 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<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.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中;先不看如何获取,先看看他是如何验证的,我们再定义个静态方法,如下:

/// <summary> /// 验证身份 验证签名的有效性, /// </summary> /// <param name="encodeJwt"></param> /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值, </param> /// 例如:payLoad["aud"]?.ToString() == "roberAuddience"; /// 例如:验证是否过期 等 /// <returns></returns> public static bool Validate(string encodeJwt,Func<Dictionary<string,object>,bool> validatePayLoad) {  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(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 < long.Parse(payLoad["exp"].ToString()));  //再其次 进行自定义的验证  success = success && validatePayLoad(payLoad);  return success; }

其中validatePayLoad 参数是一个自定义的验证的Fun,执行该Fun方法时会把解密后的payload作为参数传入进去

我们验证通过分为两部分,

第一,必须的(自认为的) 

  • jwt签名是否正确,请看以上代码实现 
  • jwt是否在可以时间内,请看以上代码实现

第二,自定义的(各复杂的,原理就是获取payLoad 的某个值,然后对这个值进行各种判读--等于,大于,包含,)  

  • 该jwt是不是进入黑名单
  • aud==‘roberAudience'

我们来通过一个测试类验证

[TestMethod]  public void TokenCustomerValidateTest()  {   Dictionary<string, object> payLoad = new Dictionary<string, object>();   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;

完整代码如下:

/// <summary> /// Token上下文,负责token的创建和验证 /// </summary> public class TokenContext {  /// <summary>  /// 秘钥,可以从配置文件中获取  /// </summary>  public static string securityKey = "GQDstclechengroberbojPOXOYg5MbeJ1XT0uFiwDVvVBrk";  /// <summary>  /// 创建jwttoken,源码自定义  /// </summary>  /// <param name="payLoad"></param>  /// <param name="header"></param>  /// <returns></returns>  public static string CreateToken(Dictionary<string, object> payLoad,int expiresMinute, Dictionary<string, object> header = null)  {   if (header == null)   {    header = new Dictionary<string, object>(new List<KeyValuePair<string, object>>() {     new KeyValuePair<string, object>("alg", "HS256"),     new KeyValuePair<string, object>("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;  }  /// <summary>  /// 创建jwtToken,采用微软内部方法,默认使用HS256加密,如果需要其他加密方式,请更改源码  /// 返回的结果和CreateToken一样  /// </summary>  /// <param name="payLoad"></param>  /// <param name="expiresMinute">有效分钟</param>  /// <returns></returns>  public static string CreateTokenByHandler(Dictionary<string, object> 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<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.FromMinutes(expiresMinute)),    signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256));   var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);   return encodedJwt;  }  /// <summary>  /// 验证身份 验证签名的有效性,  /// </summary>  /// <param name="encodeJwt"></param>  /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值, </param>  /// 例如:payLoad["aud"]?.ToString() == "roberAuddience";  /// 例如:验证是否过期 等  /// <returns></returns>  public static bool Validate(string encodeJwt,Func<Dictionary<string,object>,bool> validatePayLoad)  {   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(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 < 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); }

以上就是jwt的基本内容,它确实很简单,不要被aspnet core中的各种写法给搞晕了,只要是jwt相关的验证都是基于上面这些东西

下一章节将讲述:

  • 在aspnet core中,自定义jwt管道验证;
  • 在aspnet core中,自定义策略验证CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>
  • 自定义jwt逻辑验证和原生的角色,用户,策略,等进行对比

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

  • 相关标签:ASP.NET
  • 本文发布HTML5中文学习网 ,转载请注明出处,感谢您!
  • 相关文章


  • 曝网友假装外国人写投诉信 ofo秒退押金并回函致歉
  • 苹果市值缩水逾2000亿美元 遭多家投行下调目标价
  • Asp.net Core与类库读取配置文件信息的方法_实用技巧
  • asp.net在Repeater嵌套的Repeater中使用复选框详解_实用技巧
  • 利用IIS调试ASP.NET网站程序的完整步骤_实用技巧
  • Asp.Net Core轻松学习系列之配置文件_实用技巧
  • ASP.NET 页生命周期概述(小结)_实用技巧
  • 详解ASP.NET Core WebApi 返回统一格式参数_实用技巧
  • 2018年网络流行语有哪些?2018年十大网络流行语盘点
  • 华为首席财务官孟晚舟被暂扣 深圳市政府要求加方立即放人!
  • 独孤九贱(4)_PHP视频教程

    江湖传言:PHP是世界上最好的编程语言。真的是这样吗?这个梗究竟是从哪来的?学会本课程,你就会明白了。 PHP中文网出品的PHP入门系统教学视频,完全从初学者的角度出发,绝不玩虚的,一切以实用、有用...

    独孤九贱(5)_ThinkPHP5视频教程

    ThinkPHP是国内最流行的中文PHP开发框架,也是您Web项目的最佳选择。《php.cn独孤九贱(5)-ThinkPHP5视频教程》课程以ThinkPHP5最新版本为例,从最基本的框架常识开始,将...

    独孤九贱(1)_HTML5视频教程

    《php.cn原创html5视频教程》课程特色:php中文网原创幽默段子系列课程,以恶搞,段子为主题风格的php视频教程!轻松的教学风格,简短的教学模式,让同学们在不知不觉中,学会了HTML知识。 ...

    ThinkPHP5实战之[教学管理系统]

    本套教程,以一个真实的学校教学管理系统为案例,手把手教会您如何在一张白纸上,从零开始,一步一步的用ThinkPHP5框架快速开发出一个商业项目。

    PHP入门视频教程之一周学会PHP

    所有计算机语言的学习都要从基础开始,《PHP入门视频教程之一周学会PHP》不仅是PHP的基础部分更主要的是PHP语言的核心技术,是学习PHP必须掌握的内容,任何PHP项目的实现都离不开这部分的内容,通...

    作者信息

    kevin

    永远在学习的路上!

    相关教程

  • javascript初级视频教程 javascript初级视频教程
  • jquery 基础视频教程 jquery 基础视频教程
  • javascript三级联动视频教程 javascript三级联动视频教程
  • 独孤九贱(3)_JavaScript视频教程 独孤九贱(3)_JavaScript视频教程
  • 独孤九贱(6)_jQuery视频教程 独孤九贱(6)_jQuery视频教程
  • 热门教程