海城网站制作建设,摄影建设网站,网站怎么用PS做,给上市公司做网站有什么用前言关于JWT一共三篇 姊妹篇#xff0c;内容分别从简单到复杂#xff0c;一定要多看多想#xff1a;一、Swagger的使用 3.3 JWT权限验证【修改】二、解决JWT权限验证过期问题三、JWT完美实现权限与接口的动态分配这里一共三个文章#xff0c;目前是第一篇#xff0c;剩下两… 前言关于JWT一共三篇 姊妹篇内容分别从简单到复杂一定要多看多想 一、Swagger的使用 3.3 JWT权限验证【修改】 二、解决JWT权限验证过期问题 三、JWT完美实现权限与接口的动态分配 这里一共三个文章目前是第一篇剩下两篇主要是在博客园大家点击阅读原文自行查看就行。本文有配套视频https://www.bilibili.com/video/av58096866/?p41、如何给接口实现权限验证其实关于这一块我思考了下因为毕竟我的项目中是使用的vue api 搭建一个前台展示大部分页面都没有涉及到权限验证本来要忽略这一章节可是犹豫再三还是给大家简单分析了下个人还是希望陪大家一直搭建一个较为强大的只要是涉及到后端那一定就需要 登录》验证了根据维基百科定义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中获取“令牌”并从“令牌”中解析出该用户的身份权限然后做出相应的处理同意或拒绝返回资源 零、生成 Token 令牌关于JWT授权其实过程是很简单的大家其实这个时候静下心想一想就能明白这个就是四步走首先我们需要一个具有一定规则的 Token 令牌也就是 JWT 令牌比如我们的公司门禁卡//登录然后呢我们再定义哪些地方需要什么样的角色比如领导办公室我们是没办法进去的//授权机制接下来整个公司需要定一个规则就是如何对这个 Token 进行验证不能随便写个字条这样容易被造假比如我们公司门上的每一道刷卡机//认证方案最后就是安全部门开启认证中间件服务那这个服务可以关闭的比如我们电影里看到的黑客会把这个服务给关掉这样整个公司安保就形同虚设了。//开启中间件 那现在我们就是需要一个具有一定规则的 Token 令牌大家可以参考 JwtHelper 这个类这个实体类就是用来生成 Token 的代码记录如下 /// summary /// 颁发JWT字符串 /// /summary /// param nametokenModel/param /// returns/returns public static string IssueJwt(TokenModelJwt tokenModel) { string iss Appsettings.app(new string[] { Audience, Issuer }); string aud Appsettings.app(new string[] { Audience, Audience }); string secret AppSecretConfig.Audience_Secret_String; //var claims new Claim[] //old var claims new ListClaim { /* * 特别重要 1、这里将用户的部分信息比如 uid 存到了Claim 中如果你想知道如何在其他地方将这个 uid从 Token 中取出来请看下边的SerializeJwt() 方法或者在整个解决方案搜索这个方法看哪里使用了 2、你也可以研究下 HttpContext.User.Claims 具体的你可以看看 Policys/PermissionHandler.cs 类中是如何使用的。 */ new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ToString()), new Claim(JwtRegisteredClaimNames.Iat, ${new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}), new Claim(JwtRegisteredClaimNames.Nbf,${new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}) , //这个就是过期时间目前是过期1000秒可自定义注意JWT有自己的缓冲过期时间 new Claim (JwtRegisteredClaimNames.Exp,${new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}), new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(1000).ToString()), new Claim(JwtRegisteredClaimNames.Iss,iss), new Claim(JwtRegisteredClaimNames.Aud,aud), //new Claim(ClaimTypes.Role,tokenModel.Role),//为了解决一个用户多个角色(比如Admin,System)用下边的方法 }; // 可以将一个用户的多个角色全部赋予 // 作者DX 提供技术支持 claims.AddRange(tokenModel.Role.Split(,).Select(s new Claim(ClaimTypes.Role, s))); //秘钥 (SymmetricSecurityKey 对安全性的要求密钥的长度太短会报出异常) var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); var creds new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var jwt new JwtSecurityToken( issuer: iss, claims: claims, signingCredentials: creds); var jwtHandler new JwtSecurityTokenHandler(); var encodedJwt jwtHandler.WriteToken(jwt); return encodedJwt; } /// summary /// 令牌 /// /summary 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; } } 这里边有一个 Appsettings 类主要的作用是自动读取项目配置文件 appsettings.json 。 public class AppSecretConfig { private static string Audience_Secret Appsettings.app(new string[] { Audience, Secret }); private static string Audience_Secret_File Appsettings.app(new string[] { Audience, SecretFile }); public static string Audience_Secret_String InitAudience_Secret(); private static string InitAudience_Secret() { var securityString DifDBConnOfSecurity(Audience_Secret_File); if (!string.IsNullOrEmpty(Audience_Secret_File) !string.IsNullOrEmpty(securityString)) { return securityString; } else { return Audience_Secret; } } private static string DifDBConnOfSecurity(params string[] conn) { foreach (var item in conn) { try { if (File.Exists(item)) { return File.ReadAllText(item).Trim(); } } catch (System.Exception) { } } return conn[conn.Length - 1]; } } 这个接口如何调用呢很简单就是我们的登录api public async Taskobject GetJwtStr(string name, string pass) { string jwtStr string.Empty; bool suc false; // 获取用户的角色名请暂时忽略其内部是如何获取的可以直接用 var userRoleAdmin; 来代替更好理解。 var userRole await _sysUserInfoServices.GetUserRoleNameStr(name, pass); if (userRole ! null) { // 将用户id和角色名作为单独的自定义变量封装进 token 字符串中。 TokenModelJwt tokenModel new TokenModelJwt {Uid 1, Role userRole}; jwtStr JwtHelper.IssueJwt(tokenModel);//登录获取到一定规则的 Token 令牌 suc true; } else { jwtStr login fail!!!; } return Ok(new { success suc, token jwtStr }); } 现在我们获取到Token了那如何进行授权认证呢别着急重头戏马上到来 一、JWT授权认证流程——自定义中间件在之前的搭建中swagger已经基本成型其实其功能之多不是我这三篇所能写完的想要添加权限先从服务开始0、Swagger中开启JWT服务我们要测试 JWT 授权认证就必定要输入 Token令牌那怎么输入呢平时的话我们可以使用 Postman 来控制输入就是在请求的时候在 Header 中添加Authorization属性但是我们现在使用了 Swagger 作为接口文档那怎么输入呢别着急 Swagger 已经帮我们实现了这个录入 Token令牌的功能在startup.cs 中的 ConfigureServices - AddSwaggerGen 服务中增加以下代码注意是swagger服务内部var basePath Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;
services.AddSwaggerGen(c
{ //遍历出全部的版本做文档信息展示 typeof(ApiVersions).GetEnumNames().ToList().ForEach(version { c.SwaggerDoc(version, new OpenApiInfo { // {ApiName} 定义成全局变量方便修改 Version version, Title ${ApiName} 接口文档——Netcore 3.0, Description ${ApiName} HTTP API version, Contact new OpenApiContact { Name ApiName, Email Blog.Corexxx.com, Url new Uri(https://www.jianshu.com/u/94102b59cc2a) }, License new OpenApiLicense { Name ApiName, Url new Uri(https://www.jianshu.com/u/94102b59cc2a) } }); c.OrderActionsBy(o o.RelativePath); }); //就是这里 var xmlPath Path.Combine(basePath, Blog.Core.xml);//这个就是刚刚配置的xml文件名 c.IncludeXmlComments(xmlPath, true);//默认的第二个参数是false这个是controller的注释记得修改 var xmlModelPath Path.Combine(basePath, Blog.Core.Model.xml);//这个就是Model层的xml文件名 c.IncludeXmlComments(xmlModelPath); c.OperationFilterAddResponseHeadersFilter(); c.OperationFilterAppendAuthorizeToSummaryOperationFilter(); c.OperationFilterSecurityRequirementsOperationFilter(); #region Token绑定到ConfigureServices c.AddSecurityDefinition(oauth2, new OpenApiSecurityScheme { Description JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}注意两者之间是一个空格\, Name Authorization,//jwt默认的参数名称 In ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中) Type SecuritySchemeType.ApiKey }); #endregion
}); 然后执行代码就可以在 swagger/index.html 页面里看到这个Token入口了 大家点开看到输入框在输入Token的时候需要在Token令牌的前边加上Bearer 为什么要加这个下文会说明请一定要注意看一定要明白为啥要带因为它涉及到了什么是授权什么是认证还要自定义认证中间件还是官方认证中间件的区别请注意看下文比如是这样的但是请注意如果你使用的是中间件 app.UseMiddlewareJwtTokenAuth()或者 app.UseJwtTokenAuth() 的时候两种写法一样 要是使用 Bearer xxxx传值的时候记得在中间件的方法中把Token的 “Bearer 空格” 字符给截取掉这样的 1API接口授权策略这里可以直接在api接口上直接设置该接口所对应的角色权限信息这个时候我们就需要对每一个接口设置对应的 Roles 信息但是如果我们的接口需要对应多个角色的时候我们就可以直接写多个 这里有一个情况如果角色多的话不仅不利于我们阅读还可能在配置的时候少一两个role比如这个 api接口1 少了一个 system 的角色再比如那个 api接口2 把 Admin 角色写成了 Adnin 这种不必要的错误真是很难受那怎么办呢欸这个时候就出现了基于策略的授权机制我们在 ConfigureService 中可以这么设置// 1【授权】、这个和上边的异曲同工好处就是不用在controller中写多个 roles 。
// 然后这么写 [Authorize(Policy Admin)]
services.AddAuthorization(options
{ options.AddPolicy(Client, policy policy.RequireRole(Client).Build()); options.AddPolicy(Admin, policy policy.RequireRole(Admin).Build()); options.AddPolicy(SystemOrAdmin, policy policy.RequireRole(Admin, System));
}); 这样的话我们只需要在 controller 或者 action 上直接写策略名就可以了 [HttpGet] [Authorize(Policy SystemOrAdmin)] public ActionResultIEnumerablestring Get() { return new string[] { value1, value2 }; } 这样我们的第一步就完成了。继续走第二步身份验证方案。 关于授权认证有两种方式可以使用官方的认证方式也可以使用自定义中间件的方法具体请往下看咱们先说说如何进行自定义认证。 2、自定义认证之身份验证设置上边第一步中咱们已经对每一个接口api设置好了 授权机制 那这里就要开始认证咱们先看看如何实现自定义的认证 JwtTokenAuth一个中间件用来过滤每一个http请求就是每当一个用户发送请求的时候都先走这一步然后再去访问http请求的接口 前两步咱们都完成了从授权到自定义身份验证方案就剩下最后一步开启中间件了。 3开启自定义认证中间件实现Http信道拦截这个很简单只需要在 startup.cs - Configure 中配置认证中间件 4开始测试这个时候我们的自定义JWT授权认证已经结束了我们开始测试假设对某一个 api接口设置了权限 在我们没有输入 Token 的时候点击测试接口会报错 这个错误很明显就是说我们没有配置默认的认证方案也没有自定义身份验证方案但是这个时候我们再进行试验刚刚上边的情况是我们没有输入 Token 但是如果我们输入token呢看看是不是又会报错 我们发现了什么没有报错这是因为什么欸聪明的你应该想到了请往下看什么是 声明主体 ClaimsPrincipal 。 5、声明主体 ClaimsPrincipal 是如何保存的在上边我们解决了一些问题同时也出现了一个问题就是为什么不输入 Token 就报错了而输入了 Bearer xxxxxxxxxxx 这样的Token 就不报错了呢这里要说到 声明主体的作用了。就是我们上边写的自定义中间件大家可以再来看看 这个时候你就应该明白了吧1、首先我们自定义授权认证为啥可以不用进行下边截图中官方认证那一块的配置 因为这一块官方的服务就等同于我们的自定义身份验证方案——中间件。2、你应该明白为什么不输入token的时候报错而输入了就不报错了因为没有输入的时候直接 return了并没有在 httpContext 上下文中进行配置声明主体 httpContext.User principal 。所以说我们无论是自定义中间件的自定义身份验证方案还是官方的认证方案只要我们的登录了也就是说只要我们实现了某种规则 这样就会触发我们的内部服务将当前 token 所携带的信息进行自动解码然后填充到声明主体里自定义中间件需要手动配置官方的自动就实现该操作所以这个时候我们就可以轻松的拿到想到的东西比如这里这些 6、无策略依然授权错误上边咱们说到了如果我们自定义中间件的话在中间件中我们在 Claims 添加了角色的相关权限而且很自然的在 接口中也是分为两种情况要么没有加权限要么就是基于角色的加权 但是如果这个时候我们直接对接口增加 无任何策略 的加权 就是没有任何的策略我们登录然后添加 token一看还是报错了本来 [Authorize] 这种 无策略 的授权按理说只需要我们登录了就可以了不需要其他任何限制就可以访问但是现在依然报错401 证明我们的中间件并不能对这种方案起到效果你可能会问那带有 Roles“Admin” 的为啥可以呢反而这种无策略的不行呢我个人感觉可能还是中间件咱们设计的解决方案就是基于角色授权的那种我也再研究研究看看能不能完善下这个自定义中间件使它能适应这个 无具体策略 的加权方案但是可能写到最后就是无限接近官方的授权中间件了哈哈。这个时候我们发现自定义中间件还是挺麻烦的但是你通过自己使用自定义授权中间件不仅仅可以了解到中间件的使用还可以了解 netcore 到底是如何授权的机制但是我还是建议大家使用官方的认证方案毕竟他们考虑的很全面的。 那么如果我们想要用官方的认证方案呢要怎么写呢请往下看 二、JWT授权认证流程——官方认证上边咱们说完了自定义中间件的形式发现了也方便的地方也有不方便之处虽然灵活的使用了自定义身份验证但是毕竟很受限而且也无法对过期时间进行判断以后的文章你会看到《36 ║解决JWT自定义中间件授权过期问题》这里先不说重点说说如何通过官方认证来实现。1API接口授权策略和上边自定义的过程一模一样略。 2、官方默认认证配置在刚刚上边咱们说到了一个错误不知道还有没有印象No authenticationScheme was specified, and there was no DefaultChallengeScheme found. 就是这个自定义认证中间件呢就是前者那官方的就是后者 DefaultChallengeScheme 很简单只需要在 configureService 中添加【统一认证】即可 #region 【第二步配置认证服务】 // 令牌验证参数 var tokenValidationParameters new TokenValidationParameters { ValidateIssuerSigningKey true, IssuerSigningKey signingKey, ValidateIssuer true, ValidIssuer audienceConfig[Issuer],//发行人 ValidateAudience true, ValidAudience audienceConfig[Audience],//订阅人 ValidateLifetime true, ClockSkew TimeSpan.FromSeconds(30), RequireExpirationTime true, }; //2.1【认证】、core自带官方JWT认证 // 开启Bearer认证 services.AddAuthentication(Bearer) // 添加JwtBearer服务 .AddJwtBearer(o { o.TokenValidationParameters tokenValidationParameters; o.Events new JwtBearerEvents { OnAuthenticationFailed context { // 如果过期则把是否过期添加到返回头信息中 if (context.Exception.GetType() typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add(Token-Expired, true); } return Task.CompletedTask; } }; }); 上边代码中出现的部分参数定义如果还看不懂请看项目代码 //读取配置文件 var audienceConfig Configuration.GetSection(Audience); var symmetricKeyAsBase64 AppSecretConfig.Audience_Secret_String; var keyByteArray Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey new SymmetricSecurityKey(keyByteArray); var signingCredentials new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); 具体的每个配置的含义呢我的代码里都有大家自己可以看看都很简单。划重点我们就是用的这个官方默认的方案来替换了我们自定义中间件的身份验证方案从而达到目的说白了就是官方封装了一套方案这样我们就不用写中间件了。 3、配置官方认证中间件这个很简单还是在 configure 中添加注意中间件的顺序UseRouting放在最前边UseAuthentication在UseAuthorization前边这样就完成了结果也不用看了大家自行测试即可无论添加或者不添加 token 都不会报错。 4、补充什么是 Claim如果对 claim[] 定义不是很理解可以看看dudu大神的解释《理解ASP.NET Core验证模型(Claim, ClaimsIdentity, ClaimsPrincipal)不得不读的英文博文》这篇英文博文是 Andrew Lock 写的 Introduction to Authentication with ASP.NET Core 。 5、其他注意点1、然后再Startup的Configure中将TokenAuth注册中间件注意1HTTP管道是有先后顺序的一定要写在 app.Mvc() 之前否则不起作用。 注意2这里我们是自定义了认证中间件来对JWT的字符串进行自定义授权认证所以上边都很正常甚至我们的Token可以不用带 Bearer 特定字符串如果你以后遇到了使用官方认证中间件 UseAuthentication()那么就必须在 configureService 中对认证进行配置而且Token传递的时候也必须带上Bearer 这样的特定字符串这也就是解释了上文为啥要带Bearer这里先打个预防针因为我的最新 Github 上已经使用了官方的认证中间件所以除了上边配置的那些服务外还需要配置 Service.AddAuthentication 和 Service.AddJwtBearer 两个服务。 如果你感觉上边没看懂继续用下边的知识点来巩固吧 三、核心知识点梳理 1、Bearer认证HTTP提供了一套标准的身份验证框架服务器可以用来针对客户端的请求发送质询(challenge)客户端根据质询提供身份验证凭证。质询与应答的工作流程如下服务器端向客户端返回401Unauthorized未授权状态码并在WWW-Authenticate头中添加如何进行验证的信息其中至少包含有一种质询方式。然后客户端可以在请求中添加Authorization头进行验证其Value为身份验证的凭证信息。在HTTP标准验证方案中我们比较熟悉的是Basic和Digest前者将用户名密码使用BASE64编码后作为验证凭证后者是Basic的升级版更加安全因为Basic是明文传输密码信息而Digest是加密后传输。在前文介绍的Cookie认证属于Form认证并不属于HTTP标准验证。本文要介绍的Bearer验证也属于HTTP协议标准验证它随着OAuth协议而开始流行详细定义见 RFC 6570。A security token with the property that any party in possession of the token (a bearer) can use the token in any way that any other party in possession of it can. Using a bearer token does not require a bearer to prove possession of cryptographic key material (proof-of-possession).Bearer验证中的凭证称为BEARER_TOKEN或者是access_token它的颁发和验证完全由我们自己的应用程序来控制而不依赖于系统和Web服务器Bearer验证的标准请求方式如下Authorization: Bearer [BEARER_TOKEN]那么使用Bearer验证有什么好处呢CORS: cookies CORS 并不能跨不同的域名。而Bearer验证在任何域名下都可以使用HTTP header头部来传输用户信息。对移动端友好: 当你在一个原生平台(iOS, Android, WindowsPhone等)时使用Cookie验证并不是一个好主意因为你得和Cookie容器打交道而使用Bearer验证则简单的多。CSRF: 因为Bearer验证不再依赖于cookies, 也就避免了跨站请求攻击。标准在Cookie认证中用户未登录时返回一个302到登录页面这在非浏览器情况下很难处理而Bearer验证则返回的是标准的401 challenge。2、JWT(JSON WEB TOKEN)上面介绍的Bearer认证其核心便是BEARER_TOKEN而最流行的Token编码方式便是JSON WEB TOKEN。Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准RFC 7519。该token被设计为紧凑且安全的特别适用于分布式站点的单点登录SSO场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息以便于从资源服务器获取资源也可以增加一些额外的其它业务逻辑所必须的声明信息该token也可直接被用于认证也可被加密。JWT是由.分割的如下三部分组成头部(Header)Header 一般由两个部分组成algtypalg是所使用的hash算法如HMAC SHA256或RSAtyp是Token的类型在这里就是JWT。{alg: HS256,typ: JWT
}然后使用Base64Url编码成第一部分eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.second part.third part载荷(Payload)这一部分是JWT主要的信息存储部分其中包含了许多种的声明claims。Claims的实体一般包含用户和一些元数据这些claims分成三种类型reserved claims预定义的 一些声明并不是强制的但是推荐它们包括 iss (issuer), exp (expiration time), sub (subject),aud(audience) 等这里都使用三个字母的原因是保证 JWT 的紧凑。public claims: 公有声明这个部分可以随便定义但是要注意和 IANA JSON Web Token 冲突。private claims: 私有声明这个部分是共享被认定信息中自定义部分。一个简单的Pyload可以是这样子的{sub: 1234567890,name: John Doe,admin: true
}这部分同样使用Base64Url编码成第二部分eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.third part签名(Signature)Signature是用来验证发送者的JWT的同时也能确保在期间不被篡改。在创建该部分时候你应该已经有了编码后的Header和Payload然后使用保存在服务端的秘钥对其签名一个完整的JWT如下eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ因此使用JWT具有如下好处通用因为json的通用性所以JWT是可以进行跨语言支持的像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。紧凑JWT的构成非常简单字节占用很小可以通过 GET、POST 等放在 HTTP 的 header 中非常便于传输。扩展JWT是自我包涵的包含了必要的所有信息不需要在服务端保存会话信息, 非常易于应用的扩展。关于更多JWT的介绍网上非常多这里就不再多做介绍。下面演示一下 ASP.NET Core 中 JwtBearer 认证的使用方式。3、示例模拟TokenASP.NET Core 内置的JwtBearer验证并不包含Token的发放我们先模拟一个简单的实现[HttpPost(authenticate)]
public IActionResult Authenticate([FromBody]UserDto userDto)
{ var user _store.FindUser(userDto.UserName, userDto.Password); if (user null) return Unauthorized(); var tokenHandler new JwtSecurityTokenHandler(); var key Encoding.ASCII.GetBytes(Consts.Secret); var authTime DateTime.UtcNow; var expiresAt authTime.AddDays(7); var tokenDescriptor new SecurityTokenDescriptor { Subject new ClaimsIdentity(new Claim[] { new Claim(JwtClaimTypes.Audience,api), new Claim(JwtClaimTypes.Issuer,http://localhost:5200), new Claim(JwtClaimTypes.Id, user.Id.ToString()), new Claim(JwtClaimTypes.Name, user.Name), new Claim(JwtClaimTypes.Email, user.Email), new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber) }), Expires expiresAt, SigningCredentials new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token tokenHandler.CreateToken(tokenDescriptor); var tokenString tokenHandler.WriteToken(token); return Ok(new { access_token tokenString, token_type Bearer, profile new { sid user.Id, name user.Name, auth_time new DateTimeOffset(authTime).ToUnixTimeSeconds(), expires_at new DateTimeOffset(expiresAt).ToUnixTimeSeconds() } });
}如上使用微软提供的Microsoft.IdentityModel.Tokens帮助类(源码地址azure-activedirectory-identitymodel-extensions-for-dotnet)可以很容易的创建出JwtToen就不再多说。注册JwtBearer认证首先添加JwtBearer包引用:dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 2.0.0然后在Startup类中添加如下配置在JwtBearerOptions的配置中通常IssuerSigningKey(签名秘钥), ValidIssuer(Token颁发机构), ValidAudience(颁发给谁) 三个参数是必须的后两者用于与TokenClaims中的Issuer和Audience进行对比不一致则验证失败与上面发放Token中的Claims对应。而NameClaimType和RoleClaimType需与Token中的ClaimType一致在IdentityServer中也是使用的JwtClaimTypes否则会造成User.Identity.Name为空等问题。添加受保护资源创建一个需要授权的控制器直接使用Authorize即可[Authorize]
[Route(api/[controller])]
public class SampleDataController : Controller
{[HttpGet([action])]public IEnumerableWeatherForecast WeatherForecasts(){return ...}
}运行最后运行直接访问/api/SampleData/WeatherForecasts将返回一个401:HTTP/1.1 401 Unauthorized
Server: Kestrel
Content-Length: 0
WWW-Authenticate: Bearer让我们调用api/oauth/authenticate获取一个JWT:请求
POST http://localhost:5200/api/oauth/authenticate HTTP/1.1
content-type: application/json{username: alice,password: alice
}响应
HTTP/1.1 200 OK
{access_token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWxpY2UiLCJlbWFpbCI6ImFsaWNlQGdtYWlsLmNvbSIsInBob25lX251bWJlciI6IjE4ODAwMDAwMDAxIiwibmJmIjoxNTA5NDY0MzQwLCJleHAiOjE1MTAwNjkxNDAsImlhdCI6MTUwOTQ2NDM0MH0.Y1TDz8KjLRh_vjQ_3iYP4oJw-fmhoboiAGPqIZ-ooNc,token_type:Bearer,profile:{sid:1,name:alice,auth_time:1509464340,expires_at:1510069140}}最后使用该Token再次调用受保护资源GET http://localhost:5200/api/SampleData/WeatherForecasts HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWxpY2UiLCJlbWFpbCI6ImFsaWNlQGdtYWlsLmNvbSIsInBob25lX251bWJlciI6IjE4ODAwMDAwMDAxIiwibmJmIjoxNTA5NDY0MzQwLCJleHAiOjE1MTAwNjkxNDAsImlhdCI6MTUwOTQ2NDM0MH0.Y1TDz8KjLRh_vjQ_3iYP4oJw-fmhoboiAGPqIZ-ooNc授权成功返回了预期的数据HTTP/1.1 200 OK
Content-Type: application/json; charsetutf-8[{dateFormatted:2017/11/3,temperatureC:35,summary:Chil
l
y,temperatureF:94}]4、扩展自定义Token获取方式JwtBearer认证中默认是通过Http的Authorization头来获取的这也是最推荐的做法但是在某些场景下我们可能会使用Url或者是Cookie来传递Token那要怎么来实现呢其实实现起来非常简单如前几章介绍的一样JwtBearer也在认证的各个阶段为我们提供了事件来执行我们的自定义逻辑.AddJwtBearer(o
{o.Events new JwtBearerEvents(){OnMessageReceived context {context.Token context.Request.Query[access_token];return Task.CompletedTask;}};o.TokenValidationParameters new TokenValidationParameters{...};然后在Url中添加access_token[token]直接在浏览器中访问同样的我们也可以很容易的在Cookie中读取Token就不再演示。除了OnMessageReceived外还提供了如下几个事件TokenValidated在Token验证通过后调用。AuthenticationFailed: 认证失败时调用。Challenge: 未授权时调用。使用OIDC服务在上面的示例中我们简单模拟的Token颁发功能非常简单并不适合在生产环境中使用可是微软也没有提供OIDC服务的实现好在.NET社区中提供了几种实现可供我们选择NameDescriptionAspNet.Security.OpenIdConnect.Server (ASOS)Low-level/protocol-first OpenID Connect server framework for ASP.NET Core and OWIN/KatanaIdentityServer4OpenID Connect and OAuth 2.0 framework for ASP.NET Core - officially certified by the OpenID Foundation and under governance of the .NET FoundationOpenIddictEasy-to-use OpenID Connect server for ASP.NET CorePwdLessSimple, stateless, passwordless authentication for ASP.NET Core我们在这里使用IdentityServer4来搭建一个OIDC服务器具体代码会给大家带来混淆所以忽略了。 四、常见疑惑解析1、JWT里会存在一些用户的信息比如用户id、角色role 等等这样会不会不安全信息被泄露答JWT 本来就是一种无状态的登录授权认证用来替代每次请求都需要输入用户名密码的尴尬情况存在一些不重要的明文很正常只要不把隐私放出去就行就算是被动机不良的人得到也做不了什么事情。2、生成 JWT 的时候需要 secret 但是 解密的时候 为啥没有用到 secret 答secret的作用主要是用来防止 token 被伪造和篡改的想想上边的那个第一个问题用户得到了你的令牌获取到了你的个人信息这个是没事儿的他什么也干不了但是如果用户自己随便的生成一个 token 带上你的uid岂不是随便就可以访问资源服务器了所以这个时候就需要一个 secret 来生成 token这样的话就能保证数字签名的正确性。 而且在我们资源服务器里将token解析的时候微软封装了方法将secret进行校验了这就是保证了token的安全性从而保证我们的资源api是安全的你不信的话可以用你网站的 token 来访问我的在线项目就算是 uidrole等等全部正确还是不能访问我的网站因为你不知道我的secret所以你生成的令牌对我的是无效的。 可以看看这个视频https://www.bilibili.com/video/av52076900?share_mediumandroidshare_sourceqqbbidXZ786B57591674D68847894D8F16996AAFFB6ts1559452290064 五、结语好啦项目准备阶段就这么结束了以后咱们就可以直接用swagger来调试了而不是每次都用F5运行等接下来我们就要正式开始搭建项目了主要采用的是泛型仓储模式 RepositoryService也是一种常见的模式。六、Github本系列开源地址https://github.com/anjoy8/Blog.Core.git本文章小Demohttps://github.com/anjoy8/BlogArti/tree/master/Blog.Core_JWT 一起学习一起进步 QQ群867095512