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

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: ASP.NET Core 中jwt授权认证的流程原理

1,快速实现授权验证


什么是 JWT ?为什么要用 JWT ?JWT 的组成?

这些百度可以直接找到,这里不再赘述。


实际上,只需要知道 JWT 认证模式是使用一段 Token 作为认证依据的手段。

我们看一下 Postman 设置 Token 的位置。

微信图片_20220503115109.png

那么,如何使用 C# 的 HttpClient 访问一个 JWT 认证的 WebAPI 呢?

微信图片_20220503115112.png

下面来创建一个 ASP.NET Core 项目,尝试添加 JWT 验证功能。


1.1 添加 JWT 服务配置

在 Startup.cs 的 ConfigureServices 方法中,添加一个服务


// 设置验证方式为 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 中的中间件

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 中,添加一个这样的方法


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() 中,调用此方法

public static void Main(string[] args)
        {
            ConsoleToke();
            CreateHostBuilder(args).Build().Run();
        }


1.3 添加 API访问

我们添加一个 API。

[Authorize] 特性用于标识此 Controller 或 Action 需要使用合规的 Token 才能登录。


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

微信图片_20220503115120.png

发现报 401 (无权限)状态码,这是因为请求时不携带令牌,会导致不能访问 API。

从控制台终端复制生成的 Token 码,复制到 Postman 中,再次访问,发现响应状态码为 200,响应成功。

微信图片_20220503115123.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 是否有效。

使用特性设置相应通过认证才能访问,一般有以下情况。


// 不适用特性,可以直接访问
    public class AController : ControllerBase
    {
        public string Get() { return "666"; }
    }
    /// <summary>
    /// 整个控制器都需要授权才能访问
    /// </summary>
    [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"; }
    }
    /// <summary>
    /// 整个控制器都需要授权,但 Get 不需要
    /// </summary>
    [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 解析。


context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, JwtBearerDefaults.AuthenticationScheme);


那么,我们如何手工从原生的 Http 请求中,解析出来呢?且看我慢慢来分解步骤。

首先创建一个 TestMiddleware 文件,作为中间件使用。


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 ,自己加个判断。

贴到代码区域。


string tokenStr = context.Request.Headers["Authorization"].ToString();

Header 的 Authorization 键,是由 Breaer {Token} 组成的字符串。


2.1.2 判断是否为有效令牌

拿到 Token 后,还需要判断这个 Token 是否有效。

因为 Authorization 是由 Breaer {Token}组成,所以我们需要去掉前面的 Brear 才能获取 Token。


/// <summary>
        /// Token是否是符合要求的标准 Json Web 令牌
        /// </summary>
        /// <param name="tokenStr"></param>
        /// <returns></returns>
        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 是否符合协议规范。

将下面判断贴到代码区域。


if (!IsCanReadToken(ref tokenStr))
                return ;


2.1.3 解析 Token

下面代码可以将 Header 的 Authorization 内容转为 JwtSecurityToken 对象。

(截取字符串的方式很多种,喜欢哪个就哪个。。。)


/// <summary>
        /// 从Token解密出JwtSecurityToken,JwtSecurityToken : SecurityToken
        /// </summary>
        /// <param name="tokenStr"></param>
        /// <returns></returns>
        public JwtSecurityToken GetJwtSecurityToken(string tokenStr)
        {
            var jwt = jwtSecurityTokenHandler.ReadJwtToken(tokenStr);
            return jwt;
        }


不过这个 GetJwtSecurityToken 不是我们关注的内容,我们是要获取 Claim。

JwtSecurityToken.Claims


将下面代码贴到代码区域

JwtSecurityToken jst = GetJwtSecurityToken(tokenStr);
            IEnumerable<Claim> claims = jst.Claims;


2.1.4 生成 context.User

context.User 是一个 ClaimsPrincipal 类型,我们通过解析出的 Claim,生成 ClaimsPrincipal。


JwtSecurityToken jst = GetJwtSecurityToken(tokenStr);
            IEnumerable<Claim> claims = jst.Claims;
            List<ClaimsIdentity> ci = new List<ClaimsIdentity>() { new ClaimsIdentity(claims) };
            context.User = new ClaimsPrincipal(ci);


最终的代码块是这样的

// 我们写代码的区域
            string tokenStr = context.Request.Headers["Authorization"].ToString();
            string requestUrl = context.Request.Path.Value;
            if (!IsCanReadToken(ref tokenStr))
                return;
            JwtSecurityToken jst = GetJwtSecurityToken(tokenStr);
            IEnumerable<Claim> claims = jst.Claims;
            List<ClaimsIdentity> ci = new List<ClaimsIdentity>() { 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

以下代码可以获取所访问的节点信息。


var endpoint = context.GetEndpoint();


那么如何判断所访问的 Controller 和 Action 是否使用了认证相关的特性?

var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();


Metadata 是一个 ASP.NET Core 实现的集合对象,GetOrderedMetadata<T> 可以找出需要的特性信息。


这个集合不会区分是 Contrller 还是 Action 的 [Authorize] 特性。

那么判断 是否有 [AllowAnonymous] 特性,可以这样使用。


if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
            {
                await _next(context);
                return;
            }
相关文章
|
10天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
32 5
|
28天前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
38 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
18天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
24 3
|
2月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
20天前
|
JSON 安全 Java
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
75 5
|
4月前
|
SQL Java 测试技术
在Spring boot中 使用JWT和过滤器实现登录认证
在Spring boot中 使用JWT和过滤器实现登录认证
270 0
|
2月前
|
JSON 安全 算法
|
26天前
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
60 8
|
2月前
|
存储 安全 Java
|
5月前
|
JSON 安全 Java
使用Spring Boot和JWT实现用户认证
使用Spring Boot和JWT实现用户认证
下一篇
DataWorks