最近,团队的小伙伴们在做项目时,需要用到JWT认证。遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作。
(接上文)
6.测试运行
% dotnet run
等程序运行起来后,在浏览器输入:http://localhost:5000/swagger/,会进到Swagger的API界面。选择requestToken,点击按钮”Try it out“->”Execute“,可以看到运行结果:
["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNTg5MzgxMzQ4LCJpc3MiOiJXYW5nUGx1cyJ9.ojGuWUk9i2Vp5qu3s2UZSLC64Sm95Cao2eGF3GDVvec","123456"]
好吧,不要在意这个返回的格式。返回的两个串中,第一个就是Token,第二个是refreshToken。
到这儿,我们成功拿到了用户的Token。
拿到Token后,我们就可以进行认证操作了。
既然是认证,那应该在每个API上进行。所以,认证的过程不会放到控制器里,而应该以MiddleWare的方式,放到主流程中。
这个MiddleWare,Microsoft.AspNetCore.Authentication.JwtBearer库已经帮我们做好了。我们只需要配置就好。
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从上下文中直接取用户信息。
2.在Startup.cs里,Configure方法中,打开认证
app.UseAuthentication();
app.UseAuthorization();
这两步完成,我们就完成的认证的开发工具。
用别人的轮子还是很爽的,虽然轮子的挑选工作很复杂很费力。
3.设置API认证。
在这个Demo里,我们选代码生成时给的WeatherForecastController下的Get方法来测试。
在方法前边,我们加上Authorize:
[HttpGet]
[Authorize]
public IEnumerable<WeatherForecast> Get()
...
4.测试运行。
启动程序,跟上一章的方式一样。
程序运行后,打开:http://localhost:5000/swagger/,进入WeatherForecast,点”Try it out“->”Execute“,我们会得到一个401 - Error: Unauthorized的返回,因为我们没有做认证。
下面测试做认证后的访问。
先去requestToken拿一个Token(refreshToken这章不用),在前边加“Bearer ”,拼成一个串
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW5
要注意,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。
2.给API增加认证的角色要求
[HttpGet]
[Authorize(Roles="testUser")]
public IEnumerable<WeatherForecast> Get()
...
在这里,这个Roles="testUser"里的testUser,就是这个方法授权所对应的角色名称。
3.测试运行
按正常的步骤,取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; }
}
2.在AuthenticationController里,创建一个RefreshToken的API,并补齐验证代码
[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运行测试,能正常认证通过。
3.单独说一下refreshToken
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
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。