ASP.NET Core Web Api之JWT(一)
前言
最近沉寂了一段,主要是上半年相当于休息和调整了一段时间,接下来我将开始陆续学习一些新的技术,比如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(一)的更多相关文章
- ASP.NET Core Web Api之JWT VS Session VS Cookie(二)
前言 本文我们来探讨下JWT VS Session的问题,这个问题本没有过多的去思考,看到评论讨论太激烈,就花了一点时间去研究和总结,顺便说一句,这就是写博客的好处,一篇博客写出有的可能是经验积累,有 ...
- ASP.NET Core Web Api之JWT刷新Token(三)
前言 如题,本节我们进入JWT最后一节内容,JWT本质上就是从身份认证服务器获取访问令牌,继而对于用户后续可访问受保护资源,但是关键问题是:访问令牌的生命周期到底设置成多久呢?见过一些使用JWT的童鞋 ...
- 使用JWT创建安全的ASP.NET Core Web API
在本文中,你将学习如何在ASP.NET Core Web API中使用JWT身份验证.我将在编写代码时逐步简化.我们将构建两个终结点,一个用于客户登录,另一个用于获取客户订单.这些api将连接到在本地 ...
- ASP.NET Core Web API + Angular 仿B站(三)后台配置 JWT 的基于 token 的验证
前言: 本系列文章主要为对所学 Angular 框架的一次微小的实践,对 b站页面作简单的模仿. 本系列文章主要参考资料: 微软文档: https://docs.microsoft.com/zh-cn ...
- ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程
ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程 翻译自:地址 在今年年初,我整理了有关将JWT身份验证与ASP.NET Core Web API和Angular一起使用的详 ...
- ASP.NET Core Web API 最佳实践指南
原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 介绍 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但是,你难 ...
- Azure AD(二)调用受Microsoft 标识平台保护的 ASP.NET Core Web API 下
一,引言 上一节讲到如何在我们的项目中集成Azure AD 保护我们的API资源,以及在项目中集成Swagger,并且如何把Swagger作为一个客户端进行认证和授权去访问我们的WebApi资源的?本 ...
- 使用 Swagger 自动生成 ASP.NET Core Web API 的文档、在线帮助测试文档(ASP.NET Core Web API 自动生成文档)
对于开发人员来说,构建一个消费应用程序时去了解各种各样的 API 是一个巨大的挑战.在你的 Web API 项目中使用 Swagger 的 .NET Core 封装 Swashbuckle 可以帮助你 ...
- 在ASP.NET Core Web API上使用Swagger提供API文档
我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的API文档功能.当设置IISExpress的默认启动路由到Swagger的API文档页 ...
随机推荐
- Selenium-基础操作
一.测试代码 @Test public void test() { WebDriver driver = new FirefoxDriver(); // 打开当前包中的index页面 driver.g ...
- Installshield 在安装或者卸载过程中,判断某一程序是否正在运行
1.在操作时,首先引入类库ShutDownRunningApp.rul,其中ShutDownRunningApp.rul代码如下 /////////////////////////////////// ...
- 图像滤镜艺术---保留细节的磨皮滤镜之PS实现
原文:图像滤镜艺术---保留细节的磨皮滤镜之PS实现 目前,对于人物照片磨皮滤镜,相信大家没用过也听过吧,这个滤镜的实现方法是多种多样,有难有简,有好有差,本人经过长时间的总结,得出了一种最简单,效果 ...
- WinForm 清空界面控件值的小技巧
原文:WinForm 清空界面控件值的小技巧 在WinForm里面有时候需要清空自己输入内容或是选择的选项,以便重新操作流程,那么一般你是怎么清空界面各个控件值的呢?如果窗体里面控件,尤其是TextB ...
- mfc开发an unsupported operation was attempted错误解决
mfc开发删除了一个控件后,没有删除该控件对应的id和代码导致 觉得mfc真xx 在资源编辑可视化界面手动删除一个控件后,resource.h里该控件的ID竟然还存在 因为该id还存在,调用该控件的代 ...
- 微信小程序把玩(三十四)Audio API
原文:微信小程序把玩(三十四)Audio API 没啥可值得太注意的地方 重要属性: 1. wx.getBackgroundAudioPlayerState(object) 获取播放状态 2.wx.p ...
- C#命名约定
推荐的标识命名风格 风格名称 描述 使用建议 示例 Pascal大小写 标识符中每个单词都首字母大写 用于类型名和成员名 CarDeck, DealersHand Camel大小写 除第一个单词以外, ...
- 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 ...
- NPOI 超简单的导出导入
首先说说,第一次遇到过匿名导出的那个时候是在我在北京第一家公司,简单的声明一个对象就可以导出,那时候感觉高大上,自己也想研究研究,但是因为头将代码后来加密了根本看不到.好吧,研究了研究放弃了,后来 ...
- 使用Arcgis Api for Javascript 调用 本地Portal发布的WebMap
1.环境搭建 安装Arcgis Portal 10.4,Server 10.4,DataStore ,WebAdaptor for IIS,搭建arcgis api for javascript 4. ...