前言

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

为什么要使用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。

Json Web Token基础

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

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

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

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

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

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiSmVmZmNreSIsImVtYWlsIjoiMjc1MjE1NDg0NEBxcS5jb20iLCJleHAiOjE1NjU2MTUzOTgsIm5iZiI6MTU2MzE5NjE5OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIn0.
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 包,如下:

我们直接给出代码来创建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"),
}; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("")); var token = new JwtSecurityToken(
issuer: "http://localhost:5000",
audience: "http://localhost:5001",
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddHours(),
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(""));

如上我们给出签名的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(),
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("")); 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("")), ValidateIssuer = true,
ValidIssuer = "http://localhost:5000", ValidateAudience = true,
ValidAudience = "http://localhost:5001", ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes()
};
});

如上述若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系列,感谢阅读,下节见。

ASP.NET Core Web Api之JWT(一)的更多相关文章

  1. ASP.NET Core Web Api之JWT VS Session VS Cookie(二)

    前言 本文我们来探讨下JWT VS Session的问题,这个问题本没有过多的去思考,看到评论讨论太激烈,就花了一点时间去研究和总结,顺便说一句,这就是写博客的好处,一篇博客写出有的可能是经验积累,有 ...

  2. ASP.NET Core Web Api之JWT刷新Token(三)

    前言 如题,本节我们进入JWT最后一节内容,JWT本质上就是从身份认证服务器获取访问令牌,继而对于用户后续可访问受保护资源,但是关键问题是:访问令牌的生命周期到底设置成多久呢?见过一些使用JWT的童鞋 ...

  3. 使用JWT创建安全的ASP.NET Core Web API

    在本文中,你将学习如何在ASP.NET Core Web API中使用JWT身份验证.我将在编写代码时逐步简化.我们将构建两个终结点,一个用于客户登录,另一个用于获取客户订单.这些api将连接到在本地 ...

  4. ASP.NET Core Web API + Angular 仿B站(三)后台配置 JWT 的基于 token 的验证

    前言: 本系列文章主要为对所学 Angular 框架的一次微小的实践,对 b站页面作简单的模仿. 本系列文章主要参考资料: 微软文档: https://docs.microsoft.com/zh-cn ...

  5. ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程

    ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程 翻译自:地址 在今年年初,我整理了有关将JWT身份验证与ASP.NET Core Web API和Angular一起使用的详 ...

  6. ASP.NET Core Web API 最佳实践指南

    原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 介绍 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但是,你难 ...

  7. Azure AD(二)调用受Microsoft 标识平台保护的 ASP.NET Core Web API 下

    一,引言 上一节讲到如何在我们的项目中集成Azure AD 保护我们的API资源,以及在项目中集成Swagger,并且如何把Swagger作为一个客户端进行认证和授权去访问我们的WebApi资源的?本 ...

  8. 使用 Swagger 自动生成 ASP.NET Core Web API 的文档、在线帮助测试文档(ASP.NET Core Web API 自动生成文档)

    对于开发人员来说,构建一个消费应用程序时去了解各种各样的 API 是一个巨大的挑战.在你的 Web API 项目中使用 Swagger 的 .NET Core 封装 Swashbuckle 可以帮助你 ...

  9. 在ASP.NET Core Web API上使用Swagger提供API文档

    我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的API文档功能.当设置IISExpress的默认启动路由到Swagger的API文档页 ...

随机推荐

  1. Selenium-基础操作

    一.测试代码 @Test public void test() { WebDriver driver = new FirefoxDriver(); // 打开当前包中的index页面 driver.g ...

  2. Installshield 在安装或者卸载过程中,判断某一程序是否正在运行

    1.在操作时,首先引入类库ShutDownRunningApp.rul,其中ShutDownRunningApp.rul代码如下 /////////////////////////////////// ...

  3. 图像滤镜艺术---保留细节的磨皮滤镜之PS实现

    原文:图像滤镜艺术---保留细节的磨皮滤镜之PS实现 目前,对于人物照片磨皮滤镜,相信大家没用过也听过吧,这个滤镜的实现方法是多种多样,有难有简,有好有差,本人经过长时间的总结,得出了一种最简单,效果 ...

  4. WinForm 清空界面控件值的小技巧

    原文:WinForm 清空界面控件值的小技巧 在WinForm里面有时候需要清空自己输入内容或是选择的选项,以便重新操作流程,那么一般你是怎么清空界面各个控件值的呢?如果窗体里面控件,尤其是TextB ...

  5. mfc开发an unsupported operation was attempted错误解决

    mfc开发删除了一个控件后,没有删除该控件对应的id和代码导致 觉得mfc真xx 在资源编辑可视化界面手动删除一个控件后,resource.h里该控件的ID竟然还存在 因为该id还存在,调用该控件的代 ...

  6. 微信小程序把玩(三十四)Audio API

    原文:微信小程序把玩(三十四)Audio API 没啥可值得太注意的地方 重要属性: 1. wx.getBackgroundAudioPlayerState(object) 获取播放状态 2.wx.p ...

  7. C#命名约定

    推荐的标识命名风格 风格名称 描述 使用建议 示例 Pascal大小写 标识符中每个单词都首字母大写 用于类型名和成员名 CarDeck, DealersHand Camel大小写 除第一个单词以外, ...

  8. Dynamic linking is coming to iOS, tvOS, and watchOS ports of Qt in the 5.9 release

    http://blog.qt.io/blog/2017/01/23/qt-5-8-released/ Dynamic linking is coming to iOS, tvOS, and watch ...

  9. NPOI 超简单的导出导入

      首先说说,第一次遇到过匿名导出的那个时候是在我在北京第一家公司,简单的声明一个对象就可以导出,那时候感觉高大上,自己也想研究研究,但是因为头将代码后来加密了根本看不到.好吧,研究了研究放弃了,后来 ...

  10. 使用Arcgis Api for Javascript 调用 本地Portal发布的WebMap

    1.环境搭建 安装Arcgis Portal 10.4,Server 10.4,DataStore ,WebAdaptor for IIS,搭建arcgis api for javascript 4. ...