ASP.NET Core Web Api之JWT(一)

前言

最近沉寂了一段,主要是上半年至关于休息和调整了一段时间,接下来我将开始陆续学习一些新的技术,好比Docker、Jenkins等,都会以生活实例从零开始讲解起,到时一并和你们分享和交流。接下来几节课的内容将会讲解JWT,关于JWT的原理解析等等园子里大有文章,就再也不叙述,这里咱们讲解使用和一些注意的地方。html

为何要使用JWT

在.NET Core以前对于Web应用程序跟踪用户登陆状态最普通的方式则是使用Cookie,当用户点击登陆后将对其信息进行加密并响应写入到用户浏览器的Cookie里,当用户进行请求时,服务端将对Cookie进行解密,而后建立用户身份,整个过程都是那么顺其天然,可是这是客户端是基于浏览器的状况,若是是客户端是移动app或者桌面应用程序呢?关于JWT原理能够参考系列文章https://www.cnblogs.com/RainingNight/p/jwtbearer-authentication-in-asp-net-core.html,固然这只是其中一种限制还有其余。若是咱们使用Json Web Token简称为JWT而不是使用Cookie,此时Token将表明用户,同时咱们再也不依赖浏览器的内置机制来处理Cookie,咱们仅仅只须要请求一个Token就好。这个时候就涉及到Token认证,那么什么是Token认证呢?一言以蔽之:将令牌(咱们有时称为AccessToken或者是Bearer Token)附加到HTTP请求中并对其进行身份认证的过程。Token认证被普遍应用于移动端或SPA。ajax

Json Web Token基础

JWT由三部分构成,Base64编码的Header,Base64编码的Payload,签名,三部分经过点隔开。第一部分以Base64编码的Header主要包括Token的类型和所使用的算法,例如:算法

{
"alg": "HS265",
"typ": "JWT"
}

第二部分以Base64编码的Payload主要包含的是声明(Claims),例如,以下:json

{
    "sub": "765032130654732",
    "name": "jeffcky"
}

第三部分则是将Key经过对应的加密算法生成签名,最终三部分以点隔开,好比以下形式:api

1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
2 eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiSmVmZmNreSIsImVtYWlsIjoiMjc1MjE1NDg0NEBxcS5jb20iLCJleHAiOjE1NjU2MTUzOTgsIm5iZiI6MTU2MzE5NjE5OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIn0.
3 OJjlGJOnCCbpok05gOIgu5bwY8QYKfE2pOArtaZJbyI

到这里此时咱们应该知道:JWT包含的信息并无加密,好比为了获取Payload,咱们大可经过好比谷歌控制台中的APi(atob)对其进行解码,以下:浏览器

那如我所说既然JWT包含的信息并无加密,只是进行了Base64编码,岂不是很是不安全呢?固然不是这样,还没说完,第三部分就是签名,虽然咱们对Payload(姑且翻译为有效负载),未进行加密,可是如有蓄意更换Payload,此时签名将能充分保证Token无效,除非将签名的Key不当心暴露在光天化日之下,不然必须是安全的。好了,到了这里,咱们稍稍讲解了下JWT构成,接下来咱们进入如何在.NET Core中使用JWT。安全

.NET Core中使用JWT

在.NET Core中如何使用JWT,那么咱们必须得知晓如何建立JWT,接下来咱们首先建立一个端口号为5000的APi,建立JWT,而后咱们须要安装 System.IdentityModel.Tokens.Jwt 包,以下:app

咱们直接给出代码来建立Token,而后一一对其进行详细解释,代码以下:ide

            var claims = new Claim[]
            {
                new Claim(ClaimTypes.Name, "Jeffcky"),
                new Claim(JwtRegisteredClaimNames.Email, "2752154844@qq.com"),
                new Claim(JwtRegisteredClaimNames.Sub, "D21D099B-B49B-4604-A247-71B0518A0B1C"),
            };

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

            var token = new JwtSecurityToken(
                issuer: "http://localhost:5000",
                audience: "http://localhost:5001",
                claims: claims,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddHours(1),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );

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

 

如上咱们在声明集合中初始化声明时,咱们使用了两种方式,一个是使用 ClaimTypes ,一个是 JwtRegisteredClaimNames ,那么这两者有什么区别?以及咱们到底应该使用哪一种方式更好?或者说两种方式都使用是否有问题呢?针对ClaimTypes则来自命名空间 System.Security.Claims ,而JwtRegisteredClaimNames则来自命名空间 System.IdentityModel.Tokens.Jwt ,两者在获取声明方式上是不一样的,ClaimTypes是沿袭微软提供获取声明的方式,好比咱们要在控制器Action方法上获取上述ClaimTypes.Name的值,此时咱们须要F12查看Name的常量定义值是多少,以下:函数

接下来则是获取声明Name的值,以下:

 var sub = User.FindFirst(d => d.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")?.Value;

那么若是咱们想要获取声明JwtRegisterClaimNames.Sub的值,咱们是否是应该如上一样去获取呢?咱们来试试。

var sub = User.FindFirst(d => d.Type == JwtRegisteredClaimNames.Sub)?.Value;

此时咱们发现为空没有获取到,这是为什么呢?这是由于获取声明的方式默认是走微软定义的一套映射方式,若是咱们想要走JWT映射声明,那么咱们须要将默认映射方式给移除掉,在对应客户端Startup构造函数中,添加以下代码:

 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

若是用过并熟悉IdentityServer4的童鞋关于这点早已明了,由于在IdentityServer4中映射声明好比用户Id即(sub)是使用的JWT,也就是说使用的JwtRegisteredClaimNames,此时咱们再来获取Sub看看。

因此以上对于初始化声明两种方式的探讨并无用哪一个更好,由于对于使用ClaimTypes是沿袭以往声明映射的方式,若是要出于兼容性考虑,能够结合两种声明映射方式来使用。接下来咱们来看生成签名代码,生成签名是以下代码:

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

如上咱们给出签名的Key是1234567890123456,是否是给定Key的任意长度皆可呢,显然不是,关于Key的长度至少是16,不然会抛出以下错误

接下来咱们再来看实例化Token的参数,即以下代码:

           var token = new JwtSecurityToken(
                issuer: "http://localhost:5000",
                audience: "http://localhost:5001",
                claims: claims,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddHours(1),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );

issuer表明颁发Token的Web应用程序,audience是Token的受理者,若是是依赖第三方来建立Token,这两个参数确定必需要指定,由于第三方本就不受信任,如此设置这两个参数后,咱们可验证这两个参数。要是咱们彻底不关心这两个参数,可直接使用JwtSecurityToken的构造函数来建立Token,以下:

            var claims = new Claim[]
            {
                new Claim(ClaimTypes.Name, "Jeffcky"),
                new Claim(JwtRegisteredClaimNames.Email, "2752154844@qq.com"),
                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));

这里须要注意的是Exp和Nbf是基于Unix时间的字符串,因此上述经过实例化DateTimeOffset来建立基于Unix的时间。到了这里,咱们已经清楚的知道如何建立Token,接下来咱们来使用Token获取数据。咱们新建一个端口号为5001的Web应用程序,同时安装包【 Microsoft.AspNetCore.Authentication.JwtBearer 】接下来在Startup中ConfigureServices添加以下代码:

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

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

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

                    ValidateLifetime = true,

                    ClockSkew = TimeSpan.FromMinutes(5)
                };
            });

如上述若Token依赖于第三方而建立,此时必然会配置issuer和audience,同时在我方也如上必须验证issuer和audience,上述咱们也验证了签名,咱们经过设置 ValidateLifetime  为true,说明验证过时时间而并不是Token中的值,最后设置 ClockSkew  有效期为5分钟。对于设置 ClockSkew  除了如上方式外,还可以下设置默认也是5分钟。

 ClockSkew = TimeSpan.Zero

如上对于认证方案咱们使用的是 JwtBearerDefaults.AuthenticationScheme 即Bearer,除此以外咱们也能够自定义认证方案名称,以下:

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

            app.UseAuthentication();

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

到了这里,咱们经过端口为5000的Web Api建立了Token,并配置了端口号为5001的Web应用程序使用JWT认证,接下来最后一步则是调用端口号为5000的APi获取Token,并将Token设置到请求头中Authorization键的值,格式以下(注意Bearer后面有一个空格):

('Authorization', 'Bearer ' + token);

咱们在页面上放置一个按钮点击获取端口号为5000的Token后,接下来请求端口号为5001的应用程序,以下:

    $(function () {
        $('#btn').click(function () {
            $.get("http://localhost:5000/api/token").done(function (token) {
                $.ajax({
                    type: 'get',
                    contentType: 'application/json',
                    url: 'http://localhost:5001/api/home',
                    beforeSend: function (xhr) {
                        if (token !== null) {
                            xhr.setRequestHeader('Authorization', 'Bearer ' + token);
                        }
                    },
                    success: function (data) {
                        alert(data);
                    },
                    error: function (xhr) {
                        alert(xhr.status);
                    }
                });
            });
        });
    });

总结

本节咱们讲解了在.NET Core中使用JWT进行认证以及一点点注意事项,比较基础性的东西,下一节讲解完在JWT中使用刷新Token,开始正式进入Docker系列,感谢阅读,下节见。