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

相关文章
|
29天前
|
JSON JavaScript 数据格式
jwt-auth插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能。
jwt-auth插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能。
43 1
|
2月前
|
存储 JSON 算法
无懈可击的身份验证:深入了解JWT的工作原理
无懈可击的身份验证:深入了解JWT的工作原理
79 0
|
17天前
|
安全 数据安全/隐私保护
Springboot+Spring security +jwt认证+动态授权
Springboot+Spring security +jwt认证+动态授权
|
24天前
|
负载均衡 Cloud Native 安全
云原生最佳实践系列 6:MSE 云原生网关使用 JWT 进行认证鉴权
本文档介绍了如何在 MSE(Microservices Engine)云原生网关中集成JWT进行全局认证鉴权。
|
1月前
|
开发框架 搜索推荐 .NET
ASP.NET体检中心源码,实现检前、检中、检后全流程管理
健康体检系统遵循整个健康体检的实际流程,以提高工作效率、降低错检、防止漏检提高人性化服务水平为目的,在体检过程中可以高效、自动化、人性化的处理数据与提供服务。针对体检流程中工作强度在时间分配上不均匀等特点,解决了体检信息处理效率问题,在不增加体检中心人力资源投入或少投入的基础上,提升信息处理的效率,从而突破体检中心日处理体检人数的上限,为体检中心创造更大经济效益的同时,还能有效的降低体检工作者的劳动强度。
36 5
|
3月前
|
安全 Java 数据库
SpringSecurity+JWT前后端分离架构登录认证
在SpringSecurity实现前后端分离登录token认证详解_springsecurity前后端分离登录认证-CSDN博客基础上进行重构,实现前后端分离架构登录认证,基本思想相同,借鉴开源Gitee代码进行改造,具有更好的代码规范。
175 1
QGS
|
3月前
|
存储 JSON 算法
浅学JWT跨域认证
浅学JWT跨域认证
QGS
26 0
|
3月前
|
JSON 安全 网络安全
超详细的用户认证、权限、安全原理详解(认证、权限、JWT、RFC 7235、HTTPS、HSTS、PC端、服务端、移动端、第三方认证等等)
超详细的用户认证、权限、安全原理详解(认证、权限、JWT、RFC 7235、HTTPS、HSTS、PC端、服务端、移动端、第三方认证等等)
348 0
|
3月前
|
JSON 前端开发 算法
掌握JWT:解密身份验证和授权的关键技术
掌握JWT:解密身份验证和授权的关键技术
|
3月前
|
JSON 算法 数据安全/隐私保护
【工程构建】权限认证 JWT
【1月更文挑战第13天】【工程构建】权限认证 JWT