亲宝软件园·资讯

展开

ASP.NET Core 中jwt授权认证的流程原理

痴者工良 人气:0
[TOC] ## 1,快速实现授权验证 什么是 JWT ?为什么要用 JWT ?JWT 的组成? 这些百度可以直接找到,这里不再赘述。 实际上,只需要知道 JWT 认证模式是使用一段 Token 作为认证依据的手段。 我们看一下 Postman 设置 Token 的位置。 ![](https://img2020.cnblogs.com/blog/1315495/202003/1315495-20200315145237288-1522687558.png) 那么,如何使用 C# 的 HttpClient 访问一个 JWT 认证的 WebAPI 呢? ![](https://img2020.cnblogs.com/blog/1315495/202003/1315495-20200315145247048-773187380.png) 下面来创建一个 ASP.NET Core 项目,尝试添加 JWT 验证功能。 ### 1.1 添加 JWT 服务配置 在 Startup.cs 的 `ConfigureServices` 方法中,添加一个服务 ```C# // 设置验证方式为 Bearer Token // 你也可以添加 using Microsoft.AspNetCore.Authentication.JwtBearer; // 使用 JwtBearerDefaults.AuthenticationScheme 代替 字符串 "Brearer" services.AddAuthentication("Bearer") .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdABCD1234abcdABCD1234")), // 加密解密Token的密钥 // 是否验证发布者 ValidateIssuer = true, // 发布者名称 ValidIssuer = "server", // 是否验证订阅者 // 订阅者名称 ValidateAudience = true, ValidAudience = "client007", // 是否验证令牌有效期 ValidateLifetime = true, // 每次颁发令牌,令牌有效时间 ClockSkew = TimeSpan.FromMinutes(120) }; }); ``` 修改 `Configure` 中的中间件 ```C# app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); // 注意这里 app.UseAuthorization(); ``` 就是这么简单,通过以上设置,要求验证请求是否有权限。 ### 1.2 颁发 Token 颁发的 Token ,ASP.NET Core 不会保存。 ASP.NET Core 启用了 Token 认证,你随便将生成 Token 的代码放到不同程序的控制台,只要密钥和 Issuer 和 Audience 一致,生成的 Token 就可以登录这个 ASP.NET Core。 也就是说,可以随意创建控制台程序生成 Token,生成的 Token 完全可以登录 ASP.NET Core 程序。 至于原因,我们后面再说, 在 Program.cs 中,添加一个这样的方法 ```C# static void ConsoleToke() { // 定义用户信息 var claims = new Claim[] { new Claim(ClaimTypes.Name, "痴者工良"), new Claim(JwtRegisteredClaimNames.Email, "66666666666@qq.com"), }; // 和 Startup 中的配置一致 SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdABCD1234abcdABCD1234")); JwtSecurityToken token = new JwtSecurityToken( issuer: "server", audience: "client007", claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddMinutes(30), signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) ); string jwtToken = new JwtSecurityTokenHandler().WriteToken(token); Console.WriteLine(jwtToken); } ``` `Main()` 中,调用此方法 ```C# public static void Main(string[] args) { ConsoleToke(); CreateHostBuilder(args).Build().Run(); } ``` ### 1.3 添加 API访问 我们添加一个 API。 `[Authorize]` 特性用于标识此 Controller 或 Action 需要使用合规的 Token 才能登录。 ```C# [Authorize] [Route("api/[controller]")] [ApiController] public class HomeController : ControllerBase { public string Get() { Console.WriteLine(User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)); return "访问成功"; } } ``` 然后启动 ASP.NET Core,在 Postman 测试 访问 https://localhost/api/home。 ![](https://img2020.cnblogs.com/blog/1315495/202003/1315495-20200315145311894-1355256906.png) 发现报 401 (无权限)状态码,这是因为请求时不携带令牌,会导致不能访问 API。 从控制台终端复制生成的 Token 码,复制到 Postman 中,再次访问,发现响应状态码为 200,响应成功。 ![](https://img2020.cnblogs.com/blog/1315495/202003/1315495-20200315145325988-376843745.png) ASP.NET Core 自带 jwt 认证大概就是这样。 那么,ASP.NET Core 内部是如何实现的呢?又有哪些特性哪些坑呢?请往下看~ ## 2,探究授权认证中间件 在上面的操作中,我们在管道配置了两个中间件。 ``` app.UseAuthentication(); app.UseAuthorization(); ``` `app.UseAuthentication();` 的作用是通过 ASP.NET Core 中配置的授权认证,读取客户端中的身份标识(Cookie,Token等)并解析出来,存储到 `context.User` 中。 `app.UseAuthorization();` 的作用是判断当前访问 `Endpoint` (Controller或Action)是否使用了 `[Authorize]`以及配置角色或策略,然后校验 Cookie 或 Token 是否有效。 使用特性设置相应通过认证才能访问,一般有以下情况。 ```C# // 不适用特性,可以直接访问 public class AController : ControllerBase { public string Get() { return "666"; } } /// /// 整个控制器都需要授权才能访问 /// [Authorize] public class BController : ControllerBase { public string Get() { return "666"; } } public class CController : ControllerBase { // 只有 Get 需要授权 [Authorize] public string Get() { return "666"; } public string GetB() { return "666"; } } /// /// 整个控制器都需要授权,但 Get 不需要 /// [Authorize] public class DController : ControllerBase { [AllowAnonymous] public string Get() { return "666"; } } ``` ### 2.1 实现 Token 解析 至于 ASP.NET Core 中,`app.UseAuthentication();` 和 `app.UseAuthorization();` 的源代码各种使用了一个项目来写,代码比较多。要理解这两个中间件的作用,我们不妨来手动实现他们的功能。 解析出的 Token 是一个 ClaimsPrincipal 对象,将此对象给 `context.User` 赋值,然后在 API 中可以使用 `User` 实例来获取用户的信息。 在中间件中,使用下面的代码可以获取客户端请求的 Token 解析。 ```C# context.RequestServices.GetRequiredService().AuthenticateAsync(context, JwtBearerDefaults.AuthenticationScheme); ``` 那么,我们如何手工从原生的 Http 请求中,解析出来呢?且看我慢慢来分解步骤。 首先创建一个 TestMiddleware 文件,作为中间件使用。 ```C# public class TestMiddleware { private readonly RequestDelegate _next; jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); public TestMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // 我们写代码的区域 // 我们写代码的区域 await _next(context); } } ``` #### 2.1.1 从 Http 中获取 Token 下面代码可以中 http 请求中,取得头部的 Token 。 当然,客户端可能没有携带 Token,可能获取结果为 null ,自己加个判断。 贴到代码区域。 ```c# string tokenStr = context.Request.Headers["Authorization"].ToString(); ``` Header 的 Authorization 键,是由 `Breaer {Token} `组成的字符串。 #### 2.1.2 判断是否为有效令牌 拿到 Token 后,还需要判断这个 Token 是否有效。 因为 Authorization 是由 `Breaer {Token}`组成,所以我们需要去掉前面的 `Brear ` 才能获取 Token。 ```c# /// /// Token是否是符合要求的标准 Json Web 令牌 /// /// /// public bool IsCanReadToken(ref string tokenStr) { if (string.IsNullOrWhiteSpace(tokenStr) || tokenStr.Length < 7) return false; if (!tokenStr.Substring(0, 6).Equals(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)) return false; tokenStr = tokenStr.Substring(7); bool isCan = jwtSecurityTokenHandler.CanReadToken(tokenStr); return isCan; } ``` 获得 Token 后,通过 `JwtSecurityTokenHandler.CanReadToken(tokenStr);` 来判断 Token 是否符合协议规范。 将下面判断贴到代码区域。 ```C# if (!IsCanReadToken(ref tokenStr)) return ; ``` #### 2.1.3 解析 Token 下面代码可以将 Header 的 Authorization 内容转为 JwtSecurityToken 对象。 (截取字符串的方式很多种,喜欢哪个就哪个。。。) ```c# /// /// 从Token解密出JwtSecurityToken,JwtSecurityToken : SecurityToken /// /// /// public JwtSecurityToken GetJwtSecurityToken(string tokenStr) { var jwt = jwtSecurityTokenHandler.ReadJwtToken(tokenStr); return jwt; } ``` 不过这个 `GetJwtSecurityToken` 不是我们关注的内容,我们是要获取 Claim。 ``` JwtSecurityToken.Claims ``` 将下面代码贴到代码区域 ```c# JwtSecurityToken jst = GetJwtSecurityToken(tokenStr); IEnumerable claims = jst.Claims; ``` #### 2.1.4 生成 context.User context.User 是一个 ClaimsPrincipal 类型,我们通过解析出的 Claim,生成 ClaimsPrincipal。 ```C# JwtSecurityToken jst = GetJwtSecurityToken(tokenStr); IEnumerable claims = jst.Claims; List ci = new List() { new ClaimsIdentity(claims) }; context.User = new ClaimsPrincipal(ci); ``` 最终的代码块是这样的 ```C# // 我们写代码的区域 string tokenStr = context.Request.Headers["Authorization"].ToString(); string requestUrl = context.Request.Path.Value; if (!IsCanReadToken(ref tokenStr)) return; JwtSecurityToken jst = GetJwtSecurityToken(tokenStr); IEnumerable claims = jst.Claims; List ci = new List() { new ClaimsIdentity(claims) }; context.User = new ClaimsPrincipal(ci); var x = new ClaimsPrincipal(ci); // 我们写代码的区域 ``` ### 2.2 实现校验认证 `app.UseAuthentication();` 的大概实现过程已经做出了说明,现在我们来继续实现 `app.UseAuthorization();` 中的功能。 继续使用上面的中间件,在原代码块区域添加新的区域。 ``` // 我们写代码的区域 // 我们写的代码块 2 ``` #### 2.2.1 Endpoint Endpoint 标识了一个 http 请求所访问的路由信息和 Controller 、Action 及其特性等信息。 `[Authorize]` 特性继承了 `IAuthorizeData`。`[AllowAnonymous]` 特性继承了 `IAllowAnonymous`。 以下代码可以获取所访问的节点信息。 ```c# var endpoint = context.GetEndpoint(); ``` 那么如何判断所访问的 Controller 和 Action 是否使用了认证相关的特性? ```c# var authorizeData = endpoint?.Metadata.GetOrderedMetadata() ?? Array.Empty(); ``` Metadata 是一个 ASP.NET Core 实现的集合对象,`GetOrderedMetadata` 可以找出需要的特性信息。 这个集合不会区分是 Contrller 还是 Action 的 `[Authorize]` 特性。 那么判断 是否有 `[AllowAnonymous]` 特性,可以这样使用。 ```c# if (endpoint?.Metadata.GetMetadata() != null) { await _next(context); return; } ```

加载全部内容

相关教程
猜你喜欢
用户评论