在.net Core 3.0中使用jwt验证

 

由于工作需要写一个下载文件的api,要求要有jwt token验证,想着正好可以在Core中试试水,下面是整个开发过程;

   由于开始并不懂jwt,在网上找了些相关的资料,对于使用Core api + jwt的文章并不多,这个文章看起来有很多东西:http://www.javashuo.com/article/p-zvgqkjmo-ed.html,下面是跟着此文章进行的操作,以及自己的一些想法;

 jwt由三部分构成,基本范式是:

  1. {"alg":"HS256","typ":"JWT"}
  2. {"sub":"BY","nbf":1575448305,"exp":1575448905}
  3. 加密串1234567890123456  

详细说明: 

1.第一部分以Base64编码的Header主要包括Token的类型和所使用的算法,我的理解是定义的一个标准模板,基本使用范式即可

2.第二部分以Base64编码的Payload主要包含的是声明(Claims);我的理解是这个地方携带一些用户自定义的信息,但由于jwt的原理(下面会说),第一部分和第二部分使用的加密只是base64的编码转换,不存在加密,所以保存的信息都是次要信息

3.第三部分则是将Key通过对应的加密算法生成签名,最终三部分以点隔开;也就是通过自己的key把上面两部分加密,且这个加密是不可逆的,也就是只会存在前面两部分被修改,或者第三部分被修改,但都会失去自洽性,也就是通过这个验证;

虽然原理说起来简单,但实际操作起来就头大了,下面说下整个过程,

首先,我们先建立一个Core 3.0版的控制台,然后把必要的分层什么的建立起来

基本的分层没什么说的,mvc架构加上连数据库的MongoFactory,定义zip生成和下载方法的Ziphelper,由于不是本节重点,此处不做演示;(tokenHlper请忽略)

下面我们开始考虑加上jwt验证,都知道现在在vs里搞东西第一步就是去拿Nuget包,支持jwt的需要这两个包

System.IdentityModel.Tokens.Jwt和Microsoft.AspNetCore.Authentication.JwtBearer

然后我们需要生成jwt的token

在这我直接在 zipHelper里面写了个GetToken的方法:

public string GetToken()
        {
            var claims = new Claim[]
           {
                new Claim(JwtRegisteredClaimNames.Sub, "BY"),//此处我的理解是用户自定义部分,也就是jwt中第二部分的数据,                                                                                      //JwtRegisteredClaimNames是微软定义的一些key,其实完全可以自定义
           };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234xxxxxxxx23456"));//秘钥,这个不能暴露 长度至少是16位,否则会报错

            var token = new JwtSecurityToken(
                //issuer: "http://localhost:5000",
                //audience: "http://localhost:5001",
                claims: claims,
                notBefore: DateTime.Now,//自动给Claim里面带了个时间戳参数nbf
                expires: DateTime.Now.AddMinutes(10),//自动给Claim里面带了个过期戳参数exp
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );

            var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
            return jwtToken;
        }

issuer代表颁发Token的Web应用程序,audience是Token的受理者,如果是依赖第三方来创建Token,这两个参数肯定必须要指定,因为第三方本就不受信任,如此设置这两个参数后,我们可验证这两个参数。要是我们完全不关心这两个参数,可直接使用JwtSecurityToken的构造函数来创建Token,如下:

 var claims = new Claim[]
            {
                new Claim(ClaimTypes.Name, "Jeffcky"),
                new Claim(JwtRegisteredClaimNames.Email, "[email protected]"),
                new Claim(JwtRegisteredClaimNames.Sub, "D21D099B-B49B-4604-A247-71B0518A0B1C"),
                new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMilliseconds(1)).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}")
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456"));

            var jwtToken = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims));

然后是验证,需要在Startup里的ConfigureServices方法里加上使用jwt组件

 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456")),

                    ValidateIssuer = false,
                    //ValidIssuer = "http://localhost:5000",

                    ValidateAudience = false,
                    //ValidAudience = "http://localhost:5001",

                    ValidateLifetime = true,

                    ClockSkew = TimeSpan.FromMinutes(5)
                };
                options.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("act", "expired");
                        }
                        return Task.CompletedTask;
                    }
                };
            });

 最后别忘记添加认证中间件在Configure方法中,认证中间件必须放在使用MVC中间件之前,如下:

复制代码

            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

复制代码然后是前端页面上

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
    @*<a id="btn">测试</a>*@
    <input type="button" id="btn" value="模拟登录获取Token" />

    <input type="button" id="btn-currentTime" value="调用客户端获取当前时间" />
</div>
<script src="~/js/jquery-latest.js"></script>

<script type="text/javascript">
    $(function () {
        $('#btn').click(function () {
            var token = null;
          var getToken=   $.ajax({
                          url: "@Url.Action("GetToken","Zip")",
                          type: 'get',
                          contentType: 'application/json',
                          data: {},
                          dataType: "Json",
                          success: function (data) {
                              token = data ;
                              console.info(token)
                              CreateZipFile()
                                   },
                                   error: function (xhr) {
                                       alert(xhr.status);
                                   }
          });
            function CreateZipFile() {
                $.ajax({
                    url: "@Url.Action("CreateZipFile","Zip")",
                    type: 'get',
                    contentType: 'application/json',
                    data: {},
                    dataType: "Json",
                    beforeSend: function (xhr) {
                        console.info(token)
                        if (token !== null) {
                            xhr.setRequestHeader('Authorization', 'Bearer ' + token);
                        }
                    },
                    success: function (data) {
                        console.info(data);
                        console.info(2)
                        if (data.code == "200") {
                            window.open("api/Zip/downloadRequest?id=" + data.id);
                        }
                        else {
                            console.info(data.strerr);
                        }
                    },
                    error: function (xhr) {
                        alert(xhr.status);
                    }
                });
            }

        });

</script>

验证token的方法是直接取,看是不是能取到,就拿我这个项目里面验证的方法CreateZipFile的时候

 

 //var Claims = User.Claims.Count(); //正常是可以获取到所有的Claims的,我试验如果吧token的串修改的话这个地方就取不到了,但还是会进来这个方法,所以要判断下是不是null

var sub = User.FindFirst(d => d.Type == JwtRegisteredClaimNames.Sub)?.Value;
            if (string.IsNullOrEmpty(sub))
            {
                return Json(new { code="0",id="",strErr="Token验证失败"});
            }
            var result = _Ziphelper.CreateFileAndZip(id);

            return Json(new { code = result.Code, Id = result.Id, strErr = result.strErr });

 

暂时只有这么多了,这只是用了最简单的... 

主逻辑是把key给使用api的那边了,毕竟登录的模块没在我这边,然后他那边生成token之后,发过来这边验证,返回验证信息,如果通过的话会生成zip文件,返回值判断正常就可以下载了