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

简介:

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

1,快速实现授权验证
1.1 添加 JWT 服务配置
1.2 颁发 Token
1.3 添加 API访问
2,探究授权认证中间件
2.1 实现 Token 解析
2.2 实现校验认证
1,快速实现授权验证
什么是 JWT ?为什么要用 JWT ?JWT 的组成?

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

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

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

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

下面来创建一个 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

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

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

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 可以找出需要的特性信息。

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

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

        if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
        {
            await _next(context);
            return;
        }

原文地址https://www.cnblogs.com/whuanle/p/12497614.html

相关文章
|
6天前
|
消息中间件 前端开发 小程序
一个基于.NET Core构建的简单、跨平台、模块化的商城系统
今天大姚给大家分享一个基于.NET Core构建的简单、跨平台、模块化、完全开源免费(MIT License)的商城系统:Module Shop。
|
6天前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
23 0
|
6天前
|
Linux API iOS开发
.net core 优势
.NET Core 的优势:跨平台兼容(Windows, macOS, Linux)及容器支持,高性能,支持并行版本控制,丰富的新增API,以及开源。
28 4
|
6天前
|
开发框架 人工智能 .NET
C#/.NET/.NET Core拾遗补漏合集(持续更新)
在这个快速发展的技术世界中,时常会有一些重要的知识点、信息或细节被忽略或遗漏。《C#/.NET/.NET Core拾遗补漏》专栏我们将探讨一些可能被忽略或遗漏的重要知识点、信息或细节,以帮助大家更全面地了解这些技术栈的特性和发展方向。
|
6天前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
49 0
|
6天前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
33 0
|
6天前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
123 5
|
9月前
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
126 0
|
10月前
|
开发框架 前端开发 .NET
[回馈]ASP.NET Core MVC开发实战之商城系统(一)
[回馈]ASP.NET Core MVC开发实战之商城系统(一)
121 0
|
10月前
|
SQL 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(开篇)
[回馈]ASP.NET Core MVC开发实战之商城系统(开篇)
146 0