asp.net core 集成JWT(一)
【什么是JWT】
JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。
JWT的官网地址:https://jwt.io/
通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT令牌在api接口中校验用户的身份以确认用户是否有访问api的权限。
JWT中包含了身份认证必须的参数以及用户自定义的参数,JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
【什么时候应该使用JSON Web令牌?】
授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
【JWT有什么优势?】
我们先看我们传统的身份校验方式
- 用户向服务器发送用户名和密码。
- 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
- 服务器向用户返回一个 session_id,写入用户的 Cookie。
- 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
- 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。如果session存储的节点挂了,那么整个服务都会瘫痪,体验相当不好,风险也很高。
相比之下,JWT的实现方式是将用户信息存储在客户端,服务端不进行保存。每次请求都把令牌带上以校验用户登录状态,这样服务就变成了无状态的,服务器集群也很好扩展。
【JWT令牌结构】
在紧凑的形式中,JSON Web Tokens由dot(.
)分隔的三个部分组成,它们是:
- Header 头
- Payload 有效载荷
- Signature 签名
因此,JWT通常如下所示:
xxxxx.yyyyy.zzzzz
1.Header 头
标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
然后,这个JSON被编码为Base64Url,形成JWT的第一部分。
2.Payload 有效载荷
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。例如:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。这个 JSON 对象也要使用 Base64URL 算法转成字符串。
3.Signature 签名
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发件人是否是它所声称的人。
把他们三个全部放在一起
输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比更加紧凑。
下面显示了一个JWT,它具有先前的头和有效负载编码,并使用机密签名。
如果您想使用JWT并将这些概念付诸实践,您可以使用jwt.io Debugger来解码,验证和生成JWT。
【JSON Web令牌如何工作?】
在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web令牌。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,您不应该将令牌保留的时间超过要求。
每当用户想要访问受保护的路由或资源时,用户代理应该使用承载模式发送JWT,通常在Authorization标头中。标题的内容应如下所示:
Authorization: Bearer <token>
在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查Authorization
标头中的有效JWT ,如果存在,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库以进行某些操作的需要,尽管可能并非总是如此。
如果在标Authorization
头中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。
下图显示了如何获取JWT并用于访问API或资源:
- 应用程序向授权服务器请求授权
- 校验用户身份,校验成功,返回token
- 应用程序使用访问令牌访问受保护的资源
【ASP.Net Core 集成JWT】
前面我们介绍了JWT的原理,下面我们在asp.net core实际项目中集成JWT。
首先我们新建一个Demo asp.net core 空web项目
添加数据访问模拟api,ValuesController
其中api/value1是可以直接访问的,api/value2添加了权限校验特性标签 [Authorize]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; namespace Demo.Jwt.Controllers
{
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
[Route("api/value1")]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value1" };
} [HttpGet]
[Route("api/value2")]
[Authorize]
public ActionResult<IEnumerable<string>> Get2()
{
return new string[] { "value2", "value2" };
}
}
}
添加模拟登陆,生成Token的api,AuthController
这里模拟一下登陆校验,只验证了用户密码不为空即通过校验,真实环境完善校验用户和密码的逻辑。
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens; namespace Demo.Jwt.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
[AllowAnonymous]
[HttpGet]
public IActionResult Get(string userName, string pwd)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(pwd))
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),
new Claim(ClaimTypes.Name, userName)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: Const.Domain,
audience: Const.Domain,
claims: claims,
expires: DateTime.Now.AddMinutes(),
signingCredentials: creds); return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
else
{
return BadRequest(new { message = "username or password is incorrect." });
}
}
}
}
Startup添加JWT验证的相关配置
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Text; namespace Demo.Jwt
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//添加jwt验证:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(),
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidAudience = Const.Domain,//Audience
ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到SecurityKey
};
}); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
///添加jwt验证
app.UseAuthentication(); if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
最后把代码里面用到的一些相关常量也粘贴过来,Const.cs
namespace Demo.Jwt
{
public class Const
{
/// <summary>
/// 这里为了演示,写死一个密钥。实际生产环境可以从配置文件读取,这个是用网上工具随便生成的一个密钥
/// </summary>
public const string SecurityKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB";
public const string Domain = "http://localhost:5000";
}
}
到这里,已经是我们项目的所有代码了。
如果需要完整的项目代码,Github地址:https://github.com/sevenTiny/Demo.Jwt
【JWT测试】
我们找一个趁手的工具,比如fiddler,然后把我们的web站点运行起来
首先调用无权限的接口:http://localhost:5000/api/value1
正确地返回了数据,那么接下来我们测试JWT的流程
1. 无权限
首先我们什么都不加调用接口:http://localhost:5000/api/value2
返回了状态码401,也就是未经授权:访问由于凭据无效被拒绝。 说明JWT校验生效了,我们的接口收到了保护。
2.获取Token
调用模拟登陆授权接口:http://localhost:5000/api/Auth?userName=zhangsan&pwd=123
这里的用户密码是随便写的,因为我们模拟登陆只是校验了下非空,因此写什么都能通过
成功得到了响应
然后我们得到了一个xxx.yyy.zzz 格式的 token 值。我们把token复制出来
3.在刚才401的接口请求HEADER中添加JWT的参数,把我们的token加上去
再次调用我们的模拟数据接口,但是这次我们加了一个HEADER:http://localhost:5000/api/value2
把内容粘出来
User-Agent: Fiddler
Host: localhost:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiIxNTYwMzQ1MDIxIiwiZXhwIjoxNTYwMzQ2ODIxLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiemhhbmdzYW4iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ.x7Slk4ho1hZc8sR8_McVTB6VEYLz_v-5eaHvXtIDS-o
这里需要注意 Bearer 后面是有一个空格的,然后就是我们上一步获取到的token
嗯,没有401了,成功返回了数据
4.JWT的Token过期
我们且倒一杯开水,坐等30分钟(我们代码中设置的过期时间),然后再次调用数据接口:http://localhost:5000/api/value2
又变成了401,我们看下详细的返回数据
这里有标注,错误描述 token过期,说明我们设置的token过期时间生效了
5.JWT添加自定义的参数(比如带上用户信息)
假如我们想在认证通过的时候,直接从jwt的token中获取到登陆的用户名,该怎么操作呢?
首先在我们的获取token 的api接口里面添加一个Claim节点,key可以随便给,也可以使用已经提供好的一些预置Key,value是我们登陆的userName(仅作为演示)
然后在我们的模拟数据接口获取自定义参数
这里使用HttpContext的授权扩展方法,拿到认证的信息,我们来看下结果
请求成功返回,并且也拿到了我们一开始写入的userName
【评论区的一些问题】
1.token过期了怎么办?
token过期了说明登陆信息已经过期,需要重新登陆,跳转到登录页重新登陆获取新的token。(当然自动刷新token除外)
2.如何交换新的token
如果要保证token长期有效,可以前端在过期前调用登陆接口刷新token。或者使用SignalR轮询,定期刷新token。
3.如何强制token失效?
我们有个ValidAudience(接收人),可以利用这个标准参数,登陆时候生成一个GUID,在数据库/Redis/xxx存一份,然后验证接口的时候再把这个值拿出来去一起校验。如果值变了校验就失败了,当然,重新登陆就会刷新这个值,所以只要重新登陆,旧的token也就失效了。
4.如何应用到集群模式
当前Demo里面,我们验证jwt的所有参数都是Const常量写死的,但是在真实生产环境都是可以走统一的配置中心,所以集群场景下,一个token可以在多个服务上被验证通过,因为校验token正确的密钥和相关参数都是从配置中心获取的。
【结束】
到这里,我们JWT的简介以及asp.net core 集成JWT已经完美完成,当然了这只是一个demo,在实际的应用中需要补充和完善的地方还有很多。
这一篇文章中评论区的一些疑问我放在了下一篇文章逐一解决,有兴趣的朋友请移步下文:asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限
如果想要完整项目源码的,可以参考地址:https://github.com/sevenTiny/Demo.Jwt
如果有幸能帮助到你,高抬贵手点个star吧~
asp.net core 集成JWT(一)的更多相关文章
- asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限
[前言] 上一篇我们介绍了什么是JWT,以及如何在asp.net core api项目中集成JWT权限认证.传送门:https://www.cnblogs.com/7tiny/p/11012035.h ...
- 10分钟简单学习net core集成jwt权限认证,快速接入项目落地使用
什么是JWT JSON Web Token(JWT)是目前最流行的跨域身份验证.分布式登录.单点登录等解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的 ...
- ABP官方文档翻译 6.2.1 ASP.NET Core集成
ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Acti ...
- ASP.NET Core 基于JWT的认证(二)
ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...
- ASP.NET Core 基于JWT的认证(一)
ASP.NET Core 基于JWT的认证(一) Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计 ...
- asp.net core 集成 log4net 日志框架
asp.net core 集成 log4net 日志框架 Intro 在 asp.net core 中有些日志我们可能想输出到数据库或文件或elasticsearch等,如果不自己去实现一个 Logg ...
- [Abp 源码分析]十七、ASP.NET Core 集成
0. 简介 整个 Abp 框架最为核心的除了 Abp 库之外,其次就是 Abp.AspNetCore 库了.虽然 Abp 本身是可以用于控制台程序的,不过那样的话 Abp 就基本没什么用,还是需要集合 ...
- Asp.Net Core基于JWT认证的数据接口网关Demo
近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo.朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对 ...
- Asp.Net Core 集成 Hangfire 配置使用 Redis 存储
Hangfire 官方支持 MSSQL 与 Redis(Hangfire.Pro.Redis) 两种 ,由于我的数据库是 MYSQL ,粗略查询了一下文档,现在对 .NET Core 支持的并不够好, ...
随机推荐
- 什么是cookie(前段时间看到别人简历上把cookie和localStorage混淆了所以专门又去了解了下)
在前端面试中,有一个必问的问题:请你谈谈cookie和localStorage有什么区别啊? localStorage是H5中的一种浏览器本地存储方式,而实际上,cookie本身并不是用来做服务器存储 ...
- Vue 在beaforeCreate时获取data中的数据
众所周知,vue在beforecreate时期是获取不到data中的 数据的 但是通过一些方法可以实现在beforecreate时获取到data中的数据 暂时想到两种放发可以实现,vue在before ...
- Vsphere中ESXi主机ssh开启的三种方法
ESXi 5.5是直接安装在物理主机上的一个虚拟机系统,本质上是一个Linux系统. 平时可以通过VMware Client端或者VMware vCenter进行管理,但对于一些特殊的VMware命令 ...
- CAD交互绘制圆(网页版)
CAD绘制图像的过程中,画圆的情况是非常常见的,用户可以在控件视区点取任意一点做为圆心,再动态点取半径绘制圆. 主要用到函数说明: _DMxDrawX::DrawCircle 绘制一个圆.详细说明如下 ...
- 用list去初始化numpy的array数组 numpy的array和python中自带的list之间相互转化
http://blog.csdn.net/baiyu9821179/article/details/53365476 a=([3.234,34,3.777,6.33]) a为python的list类型 ...
- 沈南鹏@《遇见大咖》: A轮没投,投了8个月以后就证明了张一鸣是对了,在美国都没有张一鸣这种模式
沈南鹏@<遇见大咖>: A轮没投,投了8个月以后就证明了张一鸣是对了,在美国都没有张一鸣这种模式
- struct获取不到值的小错误
struct2 get set 这两个方法一定要用双骆驼命名法:getA() setA(), 而geta() seta()不行 我找了好久的错误,只能说框架这东西快捷方便,找起错误要人命
- assembly|reads to contig|contig to scaffold|coverage|depth| tandem repeats
(组装方面):SOAPdenovo ,因为采用de Bruijn graph algorithm算法和stepwise strategy ,所以排错能力高,所以我们获得高质量数据. de Bruijn ...
- 「 HDOJ P3887 」 Counting Offspring
翻译 题目描述 给你一棵树,和它的树根 $P$,并且节点从 $1\rightarrow n$ 编号,现在定义 $f(i)$ 为 $i$ 的子树中,节点编号小于 $i$ 的节点的个数. 输入格式 有多组 ...
- [LUOGU] P2920 [USACO08NOV]时间管理Time Management
见7.3测试 #include<iostream> #include<algorithm> #include<cstdio> using namespace std ...