重大更新:

这篇文章还有一个补充篇,请看完这篇后,再看这个《从壹开始前后端分离[.netCore 不定期 ] 36 ║解决JWT权限验证过期问题
<https://www.cnblogs.com/laozhang-is-phi/p/9896431.html>》,

两个是上下衔接的,主要是解决本文中,过期时间无效的问题。

 

群友反馈:

群里有小伙伴反馈,在Swagger使用的时候报错,无法看到列表,这里我说下如何调试和主要问题:

1、如果遇到问题,这样的:



 

请在浏览器 =》 F12 ==》 console 控制台 ==》点击错误信息地址



或者直接链接http://localhost:xxxxx/swagger/v1/swagger.json,就能看到错误了

 

 

 

更新:

1、目前已经实现多用户对接口的访问,也可以通过将用户的信息自定义存入到 Token 中,然后反序列化后,将该用户的多个角色一起添加到 clain
中,实现一个用户的多角色访问。你可以搜索文中 包含以下字样的地方:
注意这个可以添加多个角色声明,请注意这是一个 list


2、网友@rookie丶 <http://home.cnblogs.com/u/1080940/>    Jwt
token时效不起作用,有知道的小伙伴,可以回答下。

      这个问题已经解决:36 ║解决JWT权限验证过期问题
<https://www.cnblogs.com/laozhang-is-phi/p/9896431.html>

 

 

提要

书接上文,在前边的两篇文章中,我们简单提到了接口文档神器Swagger,《从零开始搭建自己的前后端分离【 .NET Core2.0 Api + Vue
2.0 + AOP + 分布式】框架之三 || Swagger的使用 3.1
<https://www.cnblogs.com/laozhang-is-phi/p/9495624.html>》、《从零开始搭建自己的前后端分离【 .NET
Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之四 || Swagger的使用 3.2
<https://www.cnblogs.com/laozhang-is-phi/p/9507387.html>
》,两个文章中,也对常见的几个问题做了简单的讨论,最后还剩下一个小问题,

1、如何给接口实现权限验证?

其实关于这一块,我思考了下,因为毕竟我的项目中是使用的vue + api
搭建一个前台展示,大部分页面都没有涉及到权限验证,本来要忽略这一章节,可是犹豫再三,还是给大家简单分析了下,个人还是希望陪大家一直搭建一个较为强大的,只要是涉及到后端那一定就需要 登录=》验证了,本文主要是参考网友https://www.cnblogs.com/RayWang/p/9255093.html的思路,我自己稍加改动,大家都可以看看。

根据维基百科定义,JWT(读作 [/dʒɒt/]),即JSON Web
Tokens,是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header),
消息体(payload)和签名(signature)。它是一种用于双方之间传递安全信息的表述性声明规范。JWT作为一个开放的标准(RFC
7519),定义了一种简洁的、自包含的方法,从而使通信双方实现以JSON对象的形式安全的传递信息。


以上是JWT的官方解释,可以看出JWT并不是一种只能权限验证的工具,而是一种标准化的数据传输规范。所以,只要是在系统之间需要传输简短但却需要一定安全等级的数据时,都可以使用JWT规范来传输。规范是不因平台而受限制的,这也是JWT做为授权验证可以跨平台的原因。

如果理解还是有困难的话,我们可以拿JWT和JSON类比:


JSON是一种轻量级的数据交换格式,是一种数据层次结构规范。它并不是只用来给接口传递数据的工具,只要有层级结构的数据都可以使用JSON来存储和表示。当然,JSON也是跨平台的,不管是Win还是Linux,.NET还是Java,都可以使用它作为数据传输形式。

1)客户端向授权服务系统发起请求,申请获取“令牌”。

2)授权服务根据用户身份,生成一张专属“令牌”,并将该“令牌”以JWT规范返回给客户端


3)客户端将获取到的“令牌”放到http请求的headers中后,向主服务系统发起请求。主服务系统收到请求后会从headers中获取“令牌”,并从“令牌”中解析出该用户的身份权限,然后做出相应的处理(同意或拒绝返回资源)




 

一、通过Jwt获取Token,并通过缓存记录,配合中间件实现验证

在之前的搭建中,swagger已经基本成型,其实其功能之多,不是我这三篇所能写完的,想要添加权限,先从服务开始

 

在ConfigureServices中,增加以下代码
#region Token绑定到ConfigureServices //添加header验证信息 //
c.OperationFilter<SwaggerHeader>(); var security = new Dictionary<string,
IEnumerable<string>> { { "Blog.Core", new string[] { } }, };
c.AddSecurityRequirement(security);//方案名称“Blog.Core”可自定义,上下一致即可
c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme { Description = "
JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", Name = "
Authorization",//jwt默认的参数名称 In = "header",//jwt默认存放Authorization信息的位置(请求头中)
Type ="apiKey" }); #endregion
 

 

最终的是这样的
/// <summary> /// ConfigureServices 方法 /// </summary> /// <param
name="services"></param> public void ConfigureServices(IServiceCollection
services) { services.AddMvc();#region Swagger services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new Info { Version = "v0.1.0", Title = "Blog.Core API",
Description= "框架说明文档", TermsOfService = "None", Contact = new
Swashbuckle.AspNetCore.Swagger.Contact { Name ="Blog.Core", Email = "
Blog.Core@xxx.com", Url = "https://www.jianshu.com/u/94102b59cc2a" } }); //就是这里
#region 读取xml信息 var basePath =
PlatformServices.Default.Application.ApplicationBasePath;var xmlPath =
Path.Combine(basePath,"Blog.Core.xml");//这个就是刚刚配置的xml文件名 var xmlModelPath =
Path.Combine(basePath,"Blog.Core.Model.xml");//这个就是Model层的xml文件名
c.IncludeXmlComments(xmlPath,true);//默认的第二个参数是false,这个是controller的注释,记得修改
c.IncludeXmlComments(xmlModelPath);#endregion #region Token绑定到ConfigureServices
//添加header验证信息 //c.OperationFilter<SwaggerHeader>(); var security = new
Dictionary<string, IEnumerable<string>> { { "Blog.Core", new string[] { } }, };
c.AddSecurityRequirement(security);//方案名称“Blog.Core”可自定义,上下一致即可
c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme { Description =
"JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", Name = "
Authorization",//jwt默认的参数名称 In = "header",//jwt默认存放Authorization信息的位置(请求头中)
Type ="apiKey" }); #endregion }); #endregion #region Token服务注册
services.AddSingleton<IMemoryCache>(factory => { var cache = new MemoryCache(new
MemoryCacheOptions());return cache; }); services.AddAuthorization(options => {
options.AddPolicy("Client", policy => policy.RequireRole("Client").Build());
options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());
options.AddPolicy("AdminOrClient", policy => policy.RequireRole("Admin","Client"
).Build()); });#endregion }
 

然后执行代码,就可以看到效果

 

图 1
图 2
它的作用就是,每次请求时,从Header报文中,获取密钥token,这里根据token可以进一步判断相应的权限等。

1、重要更新:

最新的Github 开源代码中,已经做了调整,需要在输入 Token的时候,前边加上 Bearer ,比如是这样的:



但是请注意!如果你使用的是中间件app.UseMiddleware<JwtTokenAuth>() ,要是使用 Bearer
xxxx传值的时候,记得在中间件的方法中,把Token的 “Bearer ”字符给截取掉,这样的:


 

 

 

接下来,就是在项目中配置5个地方(感谢 @猫出没 <https://www.cnblogs.com/mcj-jy/>
 提出歧义,其实不是我们要新建五个文件,而是5个地方,1.JwtHelper.cs(帮助类) + 2.JwtTokenAuth.cs(执行中间件) +
3.一个Token类(存放我们的token信息) + 4.在startup中的configure方法配置启动中间件 + 5. 添加 controller
特性),如下图

 图 3

 

具体来说:

2:JwtTokenAuth,一个中间件,用来过滤每一个http请求,就是每当一个用户发送请求的时候,都先走这一步,然后再去访问http请求的接口
     public Task Invoke(HttpContext httpContext) { //检测是否包含'Authorization'请求头
if (!httpContext.Request.Headers.ContainsKey("Authorization")) { return
_next(httpContext); }var tokenHeader = httpContext.Request.Headers["
Authorization"].ToString(); TokenModelJWT tm =
JwtHelper.SerializeJWT(tokenHeader);//序列化token,获取授权//授权 注意这个可以添加多个角色声明,请注意这是一个
list var claimList = new List<Claim>(); var claim = new Claim(ClaimTypes.Role,
tm.Role); claimList.Add(claim);var identity = new ClaimsIdentity(claimList); var
principal =new ClaimsPrincipal(identity); httpContext.User = principal; return
_next(httpContext); }

3:JwtHelper 一个帮助类,可以生成Token,和讲Token反序列成model
public class JwtHelper { public static string secretKey { get; set; } = "
sdfsdfsrty45634kkhllghtdgdfss345t678fs"; /// <summary> /// 颁发JWT字符串 ///
</summary> /// <param name="tokenModel"></param> /// <returns></returns> public
static string IssueJWT(TokenModelJWT tokenModel) { var dateTime =
DateTime.UtcNow;var claims = new Claim[] { new
Claim(JwtRegisteredClaimNames.Jti,tokenModel.Uid.ToString()),//Id new Claim("
Role", tokenModel.Role),//角色 new
Claim(JwtRegisteredClaimNames.Iat,dateTime.ToString(),ClaimValueTypes.Integer64)
};//秘钥 var key = new
SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtHelper.secretKey));var creds =
new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var jwt = new
JwtSecurityToken( issuer:"Blog.Core", claims: claims, //声明集合 expires:
dateTime.AddHours(2), signingCredentials: creds); var jwtHandler = new
JwtSecurityTokenHandler();var encodedJwt = jwtHandler.WriteToken(jwt); return
encodedJwt; }/// <summary> /// 解析 /// </summary> /// <param
name="jwtStr"></param> /// <returns></returns> public static TokenModelJWT
SerializeJWT(string jwtStr) { var jwtHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken= jwtHandler.ReadJwtToken(jwtStr); object role = new
object(); ; try { jwtToken.Payload.TryGetValue("Role", out role); } catch
(Exception e) { Console.WriteLine(e);throw; } var tm = new TokenModelJWT { Uid =
(jwtToken.Id).ObjToInt(), Role= role != null ? role.ObjToString() : "", };
return tm; } }
 注意:
ObjToInt() 和 ObjToString()
//这是我封装的方法,在Blog.Core.Model层的UtilConvert类中,注意命名控制是整个解决方案的,这样全局就可以使用
  

更新 2018-12-19

如果对 claim[] 定义不是很理解,可以看看dudu大神的解释《理解ASP.NET Core验证模型(Claim, ClaimsIdentity,
ClaimsPrincipal)不得不读的英文博文 <https://www.cnblogs.com/dudu/p/6367303.html>》:
这篇英文博文是 Andrew Lock 写的 Introduction to Authentication with ASP.NET Core 。
以下是简单的阅读笔记:----------------------------------- ASP.NET Core 的验证模型是 claims-based
authentication 。Claim
是对被验证主体特征的一种表述,比如:登录用户名是...,email是...,用户Id是...,其中的“登录用户名”,“email”,“用户Id”就是ClaimType。
You can think of claimsas being a statement about...That statement consists of
a name and a value. 对应现实中的事物,比如驾照,驾照中的“身份证号码:xxx”是一个claim,“姓名:xxx”是另一个claim。
一组claims构成了一个identity,具有这些claims的identity就是 ClaimsIdentity
,驾照就是一种ClaimsIdentity,可以把ClaimsIdentity理解为“证件”,驾照是一种证件,护照也是一种证件。
ClaimsIdentity的持有者就是 ClaimsPrincipal
,一个ClaimsPrincipal可以持有多个ClaimsIdentity,就比如一个人既持有驾照,又持有护照。
------------------------------------ 理解了Claim, ClaimsIdentity,
ClaimsPrincipal这三个概念,就能理解生成登录Cookie为什么要用下面的代码?var claimsIdentity = new
ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, loginName) }, "Basic");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); await
context.Authentication.SignInAsync(_cookieAuthOptions.AuthenticationScheme,
claimsPrincipal); 要用Cookie代表一个通过验证的主体,必须包含Claim, ClaimsIdentity,
ClaimsPrincipal这三个信息,以一个持有合法驾照的人做比方,ClaimsPrincipal就是持有证件的人,ClaimsIdentity就是证件,"
Basic"就是证件类型(这里假设是驾照),Claim就是驾照中的信息。
 

4:令牌类 TokenModelJWT

定义中的TokenModelJWT,是一个令牌类,主要存储个人角色信息,自己简单写一个,也可以是你登录的时候的用户信息,或者其他
public class TokenModelJWT { /// <summary> /// Id /// </summary> public long
Uid {get; set; } /// <summary> /// 角色 /// </summary> public string Role { get;
set; } /// <summary> /// 职能 /// </summary> public string Work { get; set; } }
 

5:配置权限验证

1、然后再Startup的Configure中,将TokenAuth注册中间件

注意:HTTP管道是有先后顺序的,一定要写在 app.Mvc() 之前,否则不起作用。



 

2、在需要加权限的页面中,增加特性

 
这个时候,你运行项目,发现之前写的都报错了,

图 7
别慌!是因为每次操作请求,都会经过TokenAuth
中的Invoke方法,方法中对Header信息进行过滤,因为现在Header中,并没有相应的配置信息,看到这里,你就想到了,这个特别像我们常见的[HttpGet]等特性,没错!在.Net
Core 中,到处都可以看到AOP编程,真的特别强大。

这个时候我们就用到了最开始的那个权限按钮

,图 8

没错就是这里,但是我们方法写好了,那Token如何获取呢,别急,我们新建一个LoginController,来模拟一次登录操作,简单传递几个参数,将用户角色和缓存时间传递,然后生成Token,并生成到缓存中,为之后做准备。

 
/// <summary> /// 获取JWT的重写方法,推荐这种,注意在文件夹OverWrite下 /// </summary> /// <param
name="id">id</param> /// <param name="sub">角色</param> /// <returns></returns>
[HttpGet] [Route("Token2")] public JsonResult GetJWTStr(long id = 1, string sub
="Admin") { //这里就是用户登录以后,通过数据库去调取数据,分配权限的操作 TokenModelJWT tokenModel = new
TokenModelJWT(); tokenModel.Uid= id; tokenModel.Role = sub; string jwtStr =
JwtHelper.IssueJWT(tokenModel);return Json(jwtStr); }
 

这个时候我们就得到了我们的Token

图 9
然后粘贴到我们的上图权限窗口中,还记得么

 

图 10
接下来,你再调用窗口,就发现都可以辣!

 
更新: 2018-10-23 
 如果这里你报错了,可以在中间件里进行调试,看具体的原因是什么  
 

 
 
WHAT


这一篇呢,写的比较潦草,主要是讲如何使用,具体的细节知识,还是大家摸索,还是那句话,这里只是抛砖引玉的作用哟,通过阅读本文,你会了解到,什么是JWT,如何添加配置.net
core 中间件,如何使用Token验证,在以后的项目里你就可以在登录的时候,调用Token,返回客户端,然后判断是否有相应的接口权限。

NEXT


好啦!项目准备阶段就这么结束了,以后咱们就可以直接用swagger来调试了,而不是没错都用F5运行等,接下来我们就要正式开始搭建项目了,主要采用的是泛型仓储模式
Repository+Service,也是一种常见的模式。

CODE

https://github.com/anjoy8/Blog.Core.git
<https://github.com/anjoy8/Blog.Core.git>

 

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信