知行合一|

hiwwwk

园龄:5年1个月粉丝:4关注:12

2022-02-14 20:38阅读: 158评论: 0推荐: 0

结合.NET Core学习JWT

一 JWT是什么

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

二 JWT的作用是什么

  • 授权(Authorization )
    • 例如登录。可以通过登录API获取JWT,后续每一个请求中都包含JWT。

三 JWT的结构

Header.Payload.Signature三部分组成,中间使用.连接,Head和Payload通过Base64加密

  • Header(头部)
    • Header典型的由两部分组成:声明类型
      • 算法alg,algorithm,表示签名使用的算法,默认为HMAC SHA256 写为HS256
      • 类型typ,type,表示令牌的属性,JWT令牌统一写为JWT
      • 例如:
{
  'alg':'HS256',
  'typ':'JWT'
}
  - 以上信息通过Base64加密获取第一段信息
  • Payload(有效荷载)
    • 是JWT的主体部分。包含声明(要求)。就是传递一些数据(一般是用户的信息)。
    • JWT指定了一些默认字段供选择。例如:
      • iss:发行人
      • exp:到期时间
      • sub:主题  subject 就是id
      • aud:用户
      • nbf:在此之前不可用
      • iat:发布时间
      • jti:JWT ID用于标识该JWT
    • 还可以使用自定义字段
{
  "name":"wwwk",
  "role":"admin"
}
  • 需要注意的是,默认情况下,JWT是未加密的,任何人都可以解读内容 ,因此不要构建一些机密数据的字段,防止信息泄露。除非自己尽心加密操作。

  • Signature(签名)

    • 签名是对上面两个部分的数据,通过指定的算法生成哈希,确保数据没有被篡改过。
    • 首先需要指定一个secret,然后使用头部中指定的算法,根据一下公式生成签名。
      • HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
    • 得到哈希之后,和上面两个部分通过.分割,组成JWT对象。

四 查看方式

  • 可以通过以下方式查看
    • jwt.io
    • chrome 控制台 atob (查看jwt中间Payload部分)



五 如何通过.Net Core创建JWT

1. 简单的创建

新建Web程序👉选择API模板👉关闭为HTTPS配置
image.png
将Controllers文件夹中的WeatherForecastController.cs ,重命名为JwtController.cs
将其代码修改为:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace Jwt.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class JwtController : ControllerBase
    {
        public JwtController()
        {
        }

        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new List<string>
            {
                string.Empty
            };
        }
    }
}

首先,在Get方法中创建一组声明。
需要引入的命名空间及包。
image.png
image.png

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace Jwt.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class JwtController : ControllerBase
    {
        public JwtController()
        {
        }

        [HttpGet]
        public IEnumerable<string> Get()
        {

            var claims = new Claim[]
            {
                // .NET Core 自带的一些声明
                new Claim(ClaimTypes.Name, "wwwk"),
                // JWT的声明
                new Claim(JwtRegisteredClaimNames.Email, "wwwk@qq.com"),
                // 自定义声明
                new Claim(UserClaimTypes.Id, "123456789")
            };

            // 实例token对象
            var simpleToken = new JwtSecurityToken(claims: claims);
            // 生成token
            var jwtToken = new JwtSecurityTokenHandler().WriteToken(simpleToken);

            return new List<string>
            {
                jwtToken
            };
        }
    }

    class UserClaimTypes
    {
        public const string Id = "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid";
    }
}

启动项目之后使用Postman进行调用,我们就可以获得一个简单的token
image.png
然后通过jwt.io网站,或者自行解析获得如下数据。
image.png
可以看到现在我们获取到的串只有两部分,HeaderPayloadHeader中的algnone
其中我们定义的数据也获取了出来,是一个标准的json。三种声明方式获得了三种不同的key

2. 正常JWT的创建

包含签名和基本的定义。

using System;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

namespace Jwt.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class JwtController : ControllerBase
    {
        public JwtController()
        {
        }

        [HttpGet]
        public IEnumerable<string> Get()
        {
            var claims = new Claim[]
            {
                // .NET Core 自带的一些声明
                new Claim(ClaimTypes.Name, "wwwk"),
                // JWT的声明
                new Claim(JwtRegisteredClaimNames.Email, "wwwk@qq.com"),
                // 自定义声明
                new Claim(UserClaimTypes.Id, "123456789")
            };

            // 实例token对象
            //var simpleToken = new JwtSecurityToken(claims: claims);

            // 密钥
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdefghigklmn"));

            var token = new JwtSecurityToken(
                // 发行人
                issuer: "wwwk",
                // 订阅
                audience: "wwwk",
                // 声明
                claims: claims,
                // 过期日期
                expires: DateTime.Now.AddHours(-1),
                // 数字加密,它由两部分组成,密钥和加密方式
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );
            // 生成token
            var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);

            return new List<string>
            {
                jwtToken
            };
        }
    }

    class UserClaimTypes
    {
        public const string Id = "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid";
    }
}

再次通过postman进行调用。
发现抛出了一个异常!
image.png
这个异常的意思是说咱们定义key的长度太短,对它进行了约束,最低要16字符。这里我们将key加长至16位:var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdefghigklmnop"));
再次调用接口,成功返回:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid3d3ayIsImVtYWlsIjoid3d3a0BxcS5jb20iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3ByaW1hcnlzaWQiOiIxMjM0NTY3ODkiLCJleHAiOjE2MTc3MjA4MDEsImlzcyI6Ind3d2siLCJhdWQiOiJ3d3drIn0.XcvcSvqJZGk17kR6n3CQ7Yi1vxnbxiJlXTWjfqZrQH4

JWT.IO 网站进行查看内容。algHS256Payload中的内容也显示出来了,但是左下角显示的却是无效的签名**Invalid Signature**
image.png
这是因为左下角蓝色部分的密钥和咱们的不同导致的,此时将密钥修改为abcdefghigklmnop再次尝试。
image.png
可以看到,修改密钥之后,成功显示Signature Verified密钥是很重要的,如果第三方获取到密钥就可以伪装数据进行操作,千万不能泄露!


3. 获取接口中携带的JWT内容

上面我们获取到了JWT,那怎么在调用接口的时候发送JWT并在接口内获取其数据呢?
现在,我们创建一个新的接口:

[HttpGet("Invoke")]
public IEnumerable<string> Get(string jwt)
{
    // 第一种,jwtHandle提供的read方法
    var jwtHandler = new JwtSecurityTokenHandler();
    JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwt);

    // 第二种,通过User对象接收
    var sub = User.FindFirst(p => p.Type == UserClaimTypes.Id)?.Value;

    // 第三种,通过HttpContext上下文直接进行获取
    var name = _httpContextAccessor.HttpContext.User.Identity.Name;
    var claims = _httpContextAccessor.HttpContext.User.Claims;
    var val = claims.Select(p => p.Type == JwtRegisteredClaimNames.Email).ToList();

    return new List<string>
    {
        System.Text.Json.JsonSerializer.Serialize(jwtToken),
        sub,
        name,
        System.Text.Json.JsonSerializer.Serialize(val)
    };
}

然后在Startup.cs👉ConfigureServices方法,添加services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
image.png
通过Get接口获取Token,然后通过Invoke接口进行调用结果。
image.png
可以发现,第一种获取成功,其它两种都是为null;这是因为,通过httpcontext获取,需要在服务里注册一个认证,我们才能获取到内容,进一步才能获得鉴权。

4. 添加服务

第一步:
添加NeGetMicrosoft.AspNetCore.Authentication.JwtBearer
然后在Startup.cs👉ConfigureServices方法,添加以下代码:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    // 添加IHttpContextAccessor实例到容器中
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    // token验证参数
    var tokenValidationParameters = new TokenValidationParameters
    {
        // 是否验证IssuerSigningKey
        ValidateIssuerSigningKey = true,
        // key必须都一样
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdefghigklmnop")),
        // 是否验证Issuer
        ValidateIssuer = true,
        ValidIssuer = "wwwk",
        // 是否验证 Audience 
        ValidateAudience = true,
        ValidAudience = "wwwk",
        // 总的Token有效时间 = JwtRegisteredClaimNames.Exp + ClockSkew ;
        RequireExpirationTime = true,
        // 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比.同时启用ClockSkew 
        ValidateLifetime = true,
        // 注意这是缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟
        ClockSkew = TimeSpan.Zero
     };

    // 注入服务
    services
        // 开启Bearer验证
        .AddAuthentication("Bearer")
        // 注册JwtBearer服务
        .AddJwtBearer(o =>
         {
             o.TokenValidationParameters = tokenValidationParameters
         });
}

具体的意思看注释就行,大体上就是创建**TokenValidationParameters**对象,这个对象就是验证参数的设置,看注释就能明白大概了。如果开启验证那就要和生成JWT的参数值设为一样,比如Key密钥,如果不一样那就验证失败了,可以把验证认作是一个解密的过程。
然后注入服务AddAuthentication


第二步:
使用.Net Core自带的授权中间件,这个比较重要,如果不使用的话,就无法从管道中将jwt数据加到httpcontext上下文中。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    // 验证中间件
    app.UseAuthentication();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}


第三步:
给Invoke接口添加[Authorize]特性,这样就表示这个接口需要授权才能访问。
image.png
通过Postman调用,在请求HEADER中添加Authorization,输入通过Get获取的Token
image.png
如果随便输入一个Token或不输入,则抛出401异常(下图右上角)。
image.png
简单入门完成。

本文作者:hiwwwk

本文链接:https://www.cnblogs.com/wwwk/p/15894058.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hiwwwk  阅读(158)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起