aspnet core 2.1中使用jwt从原理到精通一

目录

  1. 原理;
  2. 根据原理使用C#语言,生成jwt;
  3. 自定义验证jwt;
  4. 使用aspnetcore 中自带的类生成jwt;

学有所得

  1. 了解jwt原理;
  2. 使用C#轻松实现jwt生成和验证

原理

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作为参数传入进去

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

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

    1.  jwt签名是否正确,请看以上代码实现
    2.  jwt是否在可以时间内,请看以上代码实现
  2. 第二,自定义的(各复杂的,原理就是获取payLoad 的某个值,然后对这个值进行各种判读--等于,大于,包含,)

    1.   该jwt是不是进入黑名单
    2. 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很简单就,主要就两部

  1. 创建jwt;
  2. 验证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相关的验证都是基于上面这些东西

下一章节将讲述:

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

原文地址:https://www.cnblogs.com/lechengbo/p/9860711.html

时间: 2024-08-27 20:52:20

aspnet core 2.1中使用jwt从原理到精通一的相关文章

NET Core 1.1中使用Jwt

NET Core里Jwt的生成倒是不麻烦,就是要踩完坑才知道正确的生成姿势-- Jwt的结构 jwt的结构是{Header}.{Playload}.{Signature}三截.其中Header和Playload是base64编码字符串,Signature是签名字符串. Header是比较固定的 typ是固定的"JWT". alg是你使用的签名算法,通常有HS256和RS256两种. 例子: { "alg": "RS256", "typ&

[.Net Core] 在 Mvc 中简单使用日志组件

在 Mvc 中简单使用日志组件 基于 .Net Core 2.0,本文只是蜻蜓点水,并非深入浅出. 目录 使用内置的日志组件 简单过渡到第三方组件 - NLog 使用内置的日志 下面使用控制器 HomeController.cs 进行演示. 需要 using Microsoft.Extensions.Logging; 方案一: public class HomeController : Controller { private readonly ILogger _logger ; public

AspNet Core 2.2使用Mysql一些问题及解决方案

本文假设的你的AspNet Core 2.2的Web程序通过EntityFrameworkCore连接使用MSSQL数据库,能正常使用. 如何想转为使用Mysql,其实不难. 1.安装Mysql这个简单,从官网https://www.mysql.com/downloads/可以直接下载,无需注册网站.个人使用只需要Community社区版即可.如果下载的是zip压缩版,解压到相应位置.添加my.ini到mysql安装目标,网上可以很容易搜索找到.命令行模式进入安装目录下的bin内:(1)运行my

ASP.NET Core 2.2 : 二十七. JWT与用户授权(细化到Action)

上一章分享了如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新,本章继续进行下一步,用户授权.涉及到的例子也以上一章的为基础.(ASP.NET Core 系列目录) 一.概述 首先说一下认证(authentication)与授权(authorization),它们经常在一起工作,所以有时候会分不清楚.并且这两个英文单词长得也像兄弟.举例来说,我刷门禁卡进入公司,门禁[认证]了我是这里的员工,可以进入:但进入公司以后,我并不是所有房间都可以进,比如“机房重地,闲人免进”,我

避免在ASP.NET Core 3.0中为启动类注入服务

本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingEnvironment VS IHostEnvironent - .NET Core 3.0中的废弃类型 Part 3 - 避免在ASP.NET Core 3.0中为启动类注入服务(本篇) Part 4 - 将终端中间件转换为ASP.NET Core 3.0中的端点路由 Part 5 - 将集成测试的

【转】ASP.NET Core 2.0中的HttpContext

  ASP.NET Core 2.0中的HttpContext相较于ASP.NET Framework有一些变化,这边列出一些之间的区别.   在ASP.NET Framework中的 System.Web.HttpContext 对应 ASP.NET Core 2.0中的 Microsoft.AspNetCore.Http.HttpContext. HttpContext   HttpContext.Items转换成: IDictionary<object, object> items =

【AspNet Core】Nuget代理网站

因为访问Nuget太慢,在Dotnet Core RC2发布前,我就基于Asp.Net做了一个Nuget代理网站 这是网站地址:http://nuget.lzzy.net/ Nuget源:http://nuget.lzzy.net/api/v2 广西电信百兆带宽. 这个网站将会缓存所有访问过的API页面与包. API页面缓存的原理,第一次访问会等待服务器从Nuget上下载页面信息 下载后会替换里面的网址并保存到数据库. 第二次访问会从数据库里取出页面兵判断过期时间 如果已过期,先返回页面信息,后

在ASP.NET Core 2.0中使用CookieAuthentication

在ASP.NET Core中关于Security有两个容易混淆的概念一个是Authentication(认证),一个是Authorization(授权).而前者是确定用户是谁的过程,后者是围绕着他们允许做什么,今天的主题就是关于在ASP.NET Core 2.0中如何使用CookieAuthentication认证. 在ASP.NET Core 2.0中使用CookieAuthentication跟在1.0中有些不同,需要在ConfigureServices和Configure中分别设置,前者我

说说ASP.Net Core 2.0中的Razor Page

随着.net core2.0的发布,我们可以创建2.0的web应用了.2.0中新东西的出现,会让我们忘记老的东西,他就是Razor Page.下面的这篇博客将会介绍ASP.Net Core 2.0中的Razor Page. 在ASP.Net Core 2.0新特点之一就是支持Razor Page.今天的Razor Page是ASP.Net Core MVC中的一个子集.ASP.Net Core MVC 支持Razor Page意味着Razor Page应用从技术上来说就是MVC应用,同时Razo