结合.NET Core学习JWT


jwt.io

  • chrome 控制台 atob (查看jwt中间Payload部分)
  • ?

    五 如何通过.Net Core创建JWT

    1. 简单的创建

    新建Web程序??选择API模板??关闭为HTTPS配置

    将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 Get()
            {
                return new List
                {
                    string.Empty
                };
            }
        }
    }
    

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

    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 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
                {
                    jwtToken
                };
            }
        }
    
        class UserClaimTypes
        {
            public const string Id = "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid";
        }
    }
    

    启动项目之后使用Postman进行调用,我们就可以获得一个简单的token

    然后通过jwt.io网站,或者自行解析获得如下数据。

    可以看到现在我们获取到的串只有两部分,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 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
                {
                    jwtToken
                };
            }
        }
    
        class UserClaimTypes
        {
            public const string Id = "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid";
        }
    }
    

    再次通过postman进行调用。
    发现抛出了一个异常!

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

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid3d3ayIsImVtYWlsIjoid3d3a0BxcS5jb20iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3ByaW1hcnlzaWQiOiIxMjM0NTY3ODkiLCJleHAiOjE2MTc3MjA4MDEsImlzcyI6Ind3d2siLCJhdWQiOiJ3d3drIn0.XcvcSvqJZGk17kR6n3CQ7Yi1vxnbxiJlXTWjfqZrQH4
    

    JWT.IO 网站进行查看内容。algHS256Payload中的内容也显示出来了,但是左下角显示的却是无效的签名**Invalid Signature**

    这是因为左下角蓝色部分的密钥和咱们的不同导致的,此时将密钥修改为abcdefghigklmnop再次尝试。

    可以看到,修改密钥之后,成功显示Signature Verified密钥是很重要的,如果第三方获取到密钥就可以伪装数据进行操作,千万不能泄露!
    ?

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

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

    [HttpGet("Invoke")]
    public IEnumerable 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
        {
            System.Text.Json.JsonSerializer.Serialize(jwtToken),
            sub,
            name,
            System.Text.Json.JsonSerializer.Serialize(val)
        };
    }
    

    然后在Startup.cs??ConfigureServices方法,添加services.AddSingleton();

    通过Get接口获取Token,然后通过Invoke接口进行调用结果。

    可以发现,第一种获取成功,其它两种都是为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();
    
        // 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]特性,这样就表示这个接口需要授权才能访问。

    通过Postman调用,在请求HEADER中添加Authorization,输入通过Get获取的Token

    如果随便输入一个Token或不输入,则抛出401异常(下图右上角)。

    简单入门完成。