Dotnet core使用JWT认证授权最佳实践(二)

最近,团队的小伙伴们在做项目时,需要用到JWT认证。遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作。

第一部分:Dotnet core使用JWT认证授权最佳实践(一)

(接上文)

  1. 测试运行

% dotnet run

等程序运行起来后,在浏览器输入:http://localhost:5000/swagger/,会进到Swagger的API界面。选择requestToken,点击按钮”Try it out“->”Execute“,可以看到运行结果:

["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNTg5MzgxMzQ4LCJpc3MiOiJXYW5nUGx1cyJ9.ojGuWUk9i2Vp5qu3s2UZSLC64Sm95Cao2eGF3GDVvec","123456"]

好吧,不要在意这个返回的格式。返回的两个串中,第一个就是Token,第二个是refreshToken。

到这儿,我们成功拿到了用户的Token。

四、Token认证

拿到Token后,我们就可以进行认证操作了。

既然是认证,那应该在每个API上进行。所以,认证的过程不会放到控制器里,而应该以MiddleWare的方式,放到主流程中。

这个MiddleWare,Microsoft.AspNetCore.Authentication.JwtBearer库已经帮我们做好了。我们只需要配置就好。

  1. 在Startup.cs中,ConfigureServices方法里,添加以下内容

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从上下文中直接取用户信息。

  1. 在Startup.cs里,Configure方法中,打开认证

app.UseAuthentication();
app.UseAuthorization();

这两步完成,我们就完成的认证的开发工具。

用别人的轮子还是很爽的,虽然轮子的挑选工作很复杂很费力。

  1. 设置API认证。

在这个Demo里,我们选代码生成时给的WeatherForecastController下的Get方法来测试。

在方法前边,我们加上Authorize:

[HttpGet]
[Authorize]
public IEnumerable<WeatherForecast> Get()
...
  1. 测试运行。

启动程序,跟上一章的方式一样。

程序运行后,打开: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,我们希望只允许具有某种角色权限的用户去访问。

下面,我们对这个项目进行小量的修改,以完成这个需求。

  1. 在给用户签发Token的过程中,加入用户的角色数据。

在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。

  1. 给API增加认证的角色要求

[HttpGet]
[Authorize(Roles="testUser")]
public IEnumerable<WeatherForecast> Get()
...

在这里,这个Roles="testUser"里的testUser,就是这个方法授权所对应的角色名称。

  1. 测试运行

按正常的步骤,取Token,拼串,保存认证信息,然后去运行WeatherForecast,API能正常返回。

我们可以把代码中的testUser改成别的字符串进行测试,会返回403 - Error: Forbidden错误。

增加角色认证成功。

六、刷新Token

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。

  1. 在DTOModels下建一个RefreshTokenDTO,用作API的输入参数

using System;namespace demo.DTOModels
{public class RefreshTokenDTO{public string Token { get; set; } public string refreshToken { get; set; }}
  1. 在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运行测试,能正常认证通过。

  1. 单独说一下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

(全文完)

点「在看」,让更多人因你而受益

↘  ↘  ↘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309556.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

题目 1886: [蓝桥杯][2017年第八届真题]包子凑数(欧几里得+完全背包)

题目&#xff1a; 时间限制: 1Sec 内存限制: 128MB 提交: 1049 解决: 365 题目描述 小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼&#xff0c;其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼&#xff0c;可以认为是无限笼。 每当有顾客想买X…

[Java基础]反射获取构造方法并使用练习

Student类代码如下: package ClassObjectPack;public class Student {private String name;int age;public String address;public Student(String name, int age, String address) {this.name name;this.age age;this.address address;}public Student() {}private Studen…

Dotnet core使用JWT认证授权最佳实践(一)

最近&#xff0c;团队的小伙伴们在做项目时&#xff0c;需要用到JWT认证。遂根据自己的经验&#xff0c;整理成了这篇文章&#xff0c;用来帮助理清JWT认证的原理和代码编写操作。一、JWTJSON Web Token (JWT)是一个开放标准(RFC 7519)&#xff0c;它定义了一种紧凑的、自包含的…

[Java基础]反射获取成员变量并使用

代码如下: package ClassObjectPack;public class Student {private String name;int age;public String address;public Student(String name, int age, String address) {this.name name;this.age age;this.address address;}public Student() {}private Student(String …

2018年蓝桥杯B组题E题+快排

题目&#xff1a; E 快速排序&#xff1a;以下代码可以从数组a[]中找出第k小的元素。 它使用了类似快速排序中的分治算法&#xff0c;期望时间复杂度是O(N)的。 请仔细阅读分析源码&#xff0c;填写划线部分缺失的内容。 #include <stdio.h> int quick_select(int a[],…

Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权

在上一讲中&#xff0c;我们已经完成了一个完整的案例&#xff0c;在这个案例中&#xff0c;我们可以通过Angular单页面应用&#xff08;SPA&#xff09;进行登录&#xff0c;然后通过后端的Ocelot API网关整合IdentityServer4完成身份认证。在本讲中&#xff0c;我们会讨论在当…

[Java基础]反射获取成员变量并使用练习

代码如下: package ClassObjectPack;public class Student {private String name;int age;public String address;public Student(String name, int age, String address) {this.name name;this.age age;this.address address;}public Student() {}private Student(String …

基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(一)

上一篇文章使用AutoMapper来处理对象与对象之间的映射关系&#xff0c;本篇主要围绕定时任务和数据抓取相关的知识点并结合实际应用&#xff0c;在定时任务中循环处理爬虫任务抓取数据。开始之前可以删掉之前测试用的几个HelloWorld&#xff0c;没有什么实际意义&#xff0c;直…

题目 2285: [蓝桥杯][2018年第九届真题]螺旋折线(数论+思维)

题目&#xff1a; 题目描述 如图所示的螺旋折线经过平面上所有整点恰好一次。 对于整点(X, Y)&#xff0c;我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。 例如dis(0, 1)3, dis(-2, -1)9 给出整点坐标(X, Y)&#xff0c;你能计算出dis(X, Y)吗&…

[Java基础]反射获取成员方法并使用练习

代码如下: package ClassObjectPack;public class Student {private String name;int age;public String address;public Student(String name, int age, String address) {this.name name;this.age age;this.address address;}public Student() {}private Student(String …

读懂操作系统之虚拟内存(一)

由于个人对虚拟内存这块特别感兴趣&#xff0c;所以就直接暂且跳过其他&#xff0c;接下来将通过几篇文章进行详细讲解&#xff0c;当然其他基础内容后续在我进行相应整体学习后也会同步输出文章&#xff0c;比如操作系统概念、程序链接、进程管理、页面置换算法、流水线、浮点…

[Java基础]反射练习之越过泛型检查,运行配置文件制定内容

代码如下: package ReflectTest01;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList;public class ReflectTest01 {public static void main(String[] args) throws NoSuchMethodException, InvocationTarg…

【Ids4实战】深究配置——用户信息操作篇

&#xff08;此花无日不春风&#xff09;其实IdentityServer4的小项目已经基本完结了&#xff0c;但是我总感觉还是有很多东西没有深入挖掘和研究的&#xff0c;这不&#xff0c;二群里有小伙伴问到了一个常见的问题&#xff0c;因为我去年都见到了&#xff0c;一直没有想过去解…

题目 1885: [蓝桥杯][2017年第八届真题]分巧克力+二分

题目&#xff1a; 题目描述 儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。 小明一共有N块巧克力&#xff0c;其中第i块是Hi x Wi的方格组成的长方形。 为了公平起见&#xff0c;小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克…

Sql Server之旅——第九站 看看DML操作对索引的影响

我们都知道建索引是需要谨慎的&#xff0c;当只有利大于弊的时候才适合建&#xff0c;同时也知道建索引是需要维护成本的&#xff0c;这个维护也就在于DML操作&#xff0c;下面具体看看到底DML对索引都有哪些内幕。。。。一&#xff1a;delete操作现在大家都已经知道索引是以B树…

Coins POJ - 1742(多重背包+是否装满问题)

题意&#xff1a; 给定n种面值的硬币面值分别为WiW_{i}Wi​个数为CiC_{i}Ci​&#xff0c;问用这些硬币可以组成1~m之间的多少面值。 题目&#xff1a; People in Silverland use coins.They have coins of value A1,A2,A3…An Silverland dollar.One day Tony opened his m…

[Java基础]反射获取成员方法并使用

代码如下: package ClassObjectPack01;import ClassObjectPack.Student;import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class ReflectDemo05 {public static void main(String[] args)…

副业刚需? 恐怕并不靠谱!

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份上一篇文章推了我的星球&#xff0c;两天时间就有接近200位读者加入。有「热心朋友」帮我计算了下&#xff1a;你这两天收入快2万啊&#xff0c;你这副业做的挺好啊。很不客气的说&#xff0c;如果写写公号&a…

Wooden Sticks POJ - 1065(最大上升子序列+动态规划状态转移思维)

题意&#xff1a; 给你n个木棍的长度和重量&#xff0c;让其成为上升序列&#xff0c;如果不能达到&#xff0c;就需要重新一分钟设置。 a&#xff09;第一个木棍的准备时间为1分钟。 b&#xff09;在处理长度为l和重量为w的棒之后&#xff0c;如果l < l’并且w < w’&a…