最近,团队的小伙伴们在作项目时,须要用到JWT认证。遂根据本身的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操做。html
第一部分:Dotnet core使用JWT认证受权最佳实践(一)前端
(接上文)git
% dotnet run
等程序运行起来后,在浏览器输入:http://localhost:5000/swagger/,会进到Swagger的API界面。选择requestToken,点击按钮”Try it out“->”Execute“,能够看到运行结果:github
["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNTg5MzgxMzQ4LCJpc3MiOiJXYW5nUGx1cyJ9.ojGuWUk9i2Vp5qu3s2UZSLC64Sm95Cao2eGF3GDVvec","123456"]
好吧,不要在乎这个返回的格式。返回的两个串中,第一个就是Token,第二个是refreshToken。web
到这儿,咱们成功拿到了用户的Token。数据库
为了防止不提供原网址的转载,特在这里加上原文连接:http://www.javashuo.com/article/p-sqxzwszx-dk.htmlc#
拿到Token后,咱们就能够进行认证操做了。浏览器
既然是认证,那应该在每一个API上进行。因此,认证的过程不会放到控制器里,而应该以MiddleWare的方式,放到主流程中。安全
这个MiddleWare,Microsoft.AspNetCore.Authentication.JwtBearer库已经帮咱们作好了。咱们只须要配置就好。bash
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(option =>
{
option.RequireHttpsMetadata = false;
option.SaveToken = true;
var token = Configuration.GetSection("tokenParameter").Get<tokenParameter>();
option.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidateIssuer = true,
ValidateAudience = false,
};
});
这里面,有几个参数须要注意:
RequireHttpsMetadata: 限定认证操做是否必须经过https来作,这个要跟随项目在生产环境中的运行状况来定。若是WebServer是我前文15分钟从零开始搭建支持10w+用户的生产环境(三)中介绍的Jexus,采用对外https,对内http的方式,那这儿能够设为false。
SaveToken: 决定Token在认证完成后,是否须要保存到上下文里并向后传。这个设置也要看应用。咱们Token生成后,用户的相关信息已经包含在里面了。API里若是有涉及用户的操做,按理能够不用往API里传相关用户的参数。一方面不安全,另外一方面代码也很差看。这时就能够把这个参数设为True,而后API从上下文中直接取用户信息。
app.UseAuthentication();
app.UseAuthorization();
这两步完成,咱们就完成的认证的开发工具。
用别人的轮子仍是很爽的,虽然轮子的挑选工做很复杂很费力。
在这个Demo里,咱们选代码生成时给的WeatherForecastController下的Get方法来测试。
在方法前边,咱们加上Authorize:
[HttpGet]
[Authorize]
public IEnumerable<WeatherForecast> Get()
...
启动程序,跟上一章的方式同样。
程序运行后,打开:http://localhost:5000/swagger/,进入WeatherForecast,点”Try it out“->”Execute“,咱们会获得一个401 - Error: Unauthorized的返回,由于咱们没有作认证。
下面测试作认证后的访问。
先去requestToken拿一个Token(refreshToken这章不用),在前边加“Bearer ”,拼成一个串
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNTg5MzgxMzQ4LCJpc3MiOiJXYW5nUGx1cyJ9.ojGuWUk9i2Vp5qu3s2UZSLC64Sm95Cao2eGF3GDVvec
要注意,Bearer后边要跟一个空格。这个串的格式是:Bearer + 空格 + Token。
在页面的右上角,有一个“Authorize”,点进去,在Value输入框中粘贴上面拼好的串,而后点按钮“Authorize”,保存认证信息。
下面进入WeatherForecast,点”Try it out“->”Execute“,这时候,咱们就能拿到正确的返回数据。
在上一章中,咱们实现了用户的认证。但这个认证有个不漂亮的地方:用户只简单的被认证系统分红了经过认证的和不经过认证的。
在实际项目中,咱们有时候会有这样的需求:对于某个API,咱们但愿只容许具备某种角色权限的用户去访问。
下面,咱们对这个项目进行小量的修改,以完成这个需求。
在AuthenticationController的RequestToken中,咱们构建了一个用户的Claims:
var claims = new[]
{
new Claim(ClaimTypes.Name,request.username),
};
就是这儿。咱们在这儿加入用户的角色:
var claims = new[]
{
new Claim(ClaimTypes.Name,request.username),
new Claim(ClaimTypes.Role, "testUser"),
};
实际应用中,这个角色的名称,能够根据须要,从用户系统中拿来。
在这个Demo里,就直接写成个字符串了。就是说,有一个角色,叫testUser。
[HttpGet]
[Authorize(Roles="testUser")]
public IEnumerable<WeatherForecast> Get()
...
在这里,这个Roles="testUser"里的testUser,就是这个方法受权所对应的角色名称。
按正常的步骤,取Token,拼串,保存认证信息,而后去运行WeatherForecast,API能正常返回。
咱们能够把代码中的testUser改为别的字符串进行测试,会返回403 - Error: Forbidden错误。
增长角色认证成功。
Token过时后,就须要刷新。
固然咱们能够把Token设成永远不过时,但这不是个安全的作法。还能够在Token过时后从新请求一个新Token,但这样作会显得Low。
赏心悦目的作法是:用refreshToken来刷新Token。设置refreshToken的过时时间长于Token。Token过时后,让用户提交Token和refreshToken到服务器,服务器验证Token是否合法,并从中提取用户信息,根据用户信息和refreshToken核验是否匹配。若是匹配,就从新生成Token给用户。
至于refreshToken的过时时长,和是否须要在刷新Token时也刷新refreshToken,就看心情了,没有固定的作法。我本身的项目中,Token是2小时过时,refreshToken是24小时过时。在Token刷新时,若是refreshToken的过时时间少于6小时,则刷新refreshToken。供参考。
下面,按这个方式,作一下刷新Token。
using System;
namespace demo.DTOModels
{
public class RefreshTokenDTO
{
public string Token { get; set; }
public string refreshToken { get; set; }
}
[HttpPost, Route("refreshToken")]
public ActionResult RefreshToken([FromBody]RefreshTokenDTO request)
{
if(request.Token == null && request.refreshToken == null)
return BadRequest("Invalid Request");
//这儿是验证Token的代码
var handler = new JwtSecurityTokenHandler();
try
{
ClaimsPrincipal claim = handler.ValidateToken(request.Token, new TokenValidationParameters{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_tokenParameter.Secret)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
}, out SecurityToken securityToken);
var username = claim.Identity.Name;
//这儿是生成Token的代码
var token = GenUserToken(username, "testUser");
var refreshToken = "654321";
return Ok(new[] { token, refreshToken });
}
catch(Exception)
{
return BadRequest("Invalid Request");
}
}
这样,Token刷新就完成了。能够用生成Token运行测试,能正常认证经过。
refreshToken,名义上是为了刷新Token,实际上用处主要是给用户从新登陆作计时。refreshToken过时了,用户就必须从新登陆。就是这么个做用。要否则,Token本身刷新岂不更好?
refreshToken能够采用跟Token同样的生成方式。可是,咱们也看到,Token生成出来的串就很长,若是refreshToken也那样生成,那就也会是一个很长的串。这样会加大前端到API的传输量。所以,这不算是一个好主意。
通常来讲,refreshToken会换一种生成方式。惟一序列、Hash,都是能够选择的,能够减小不少传输。
至于持久化和过时,依托数据库就行了。
最后,送你们一个彩蛋。
在生成Token时,咱们把过时时间设置成少于五分钟的时长,比方3分钟。但这时,实测会发现,Token的过时失效了。
为何呢?
TokenValidationParameters有一个属性叫ClockSkew,这个参数有个默认值是TimeSpan.FromMinutes(5)。
这个参数的意义是:考虑到各个服务器之间的时间不必定彻底同步,系统给了个5分钟的偏差时间。
这个偏差时间致使的结果是:少于五分钟的过时时间,会在实际认证检查时被忽略。
这个状况,Microsoft上有N多人在讨论,能够本身去查。
因此,当Token的过时小于5分钟时,想要让认证对这个时间生效,能够把这个值设为TimeSpan.Zero。
option.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidateIssuer = true,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero, //就是这一行
};
我把上面的代码,传到了Github上,须要了能够拉下来直接测试。
代码地址:https://github.com/humornif/Demo-Code/tree/master/0007/demo
(全文完)
![]() |
微信公众号:老王Plus 扫描二维码,关注我的公众号,能够第一时间获得最新的我的文章和内容推送 本文版权归做者全部,转载请保留此声明和原文连接 |