NETCore中使用JWT

    技术2023-03-25  116

    NETCore中使用JWT

    什么是JWT?

    ​ JSON Web Token(简称JWT),是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。

    JWT常用于哪些场景?

    ​ 授权:适用于单点登录。例:当用户登录成功时,服务器会返回一个token给当前登录用户,且用户登录后访问系统的各个模块都应携带该token进行请求,当token过期时,则不允许用户访问系统模块。

    ​ 信息交换:jwt是各端(客户端、服务器端)之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),发送方与接收方可通过约定好的加密秘钥进行数据的解析。

    JWT令牌结构

    ​ JWT以紧凑的形式由三部分组成,这些部分由点(.)分隔,分别是:header(头部)、payload(有效载荷)和signature(签名),即结构为header.payload.signature。

    1、header(头部)

    ​ 头部是令牌的第一部分,通常由两部分组成:令牌的类型(即JWT)和令牌所使用的签名算法,如SHA256、HMAC等。header示例如下:

    { "alg": "HS256", "typ": "JWT" }

    ​ 通过Base64对上述JSON编码后的结果为eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9,并作为JWT的第一部分。

    2、payload(有效载荷)

    ​ 有效载荷是令牌的第二部分,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。主要有以下三种类型: registered(注册的), public(公开的)和 private claims(私有的声明)。

    ​ (1)注册声明(非强制性声明):主要包含iss(jwt发布者)、sub(面向的用户)、aud(接收方)、exp(过期时间)、iat(jwt签发时间)、jti(jwt身份标识)、nbf(在某个时间点前的token不可用)

    ​ (2)公开的声明:使用JWT的人员可以随意定义上述声明。

    ​ (3)私有声明:提供方和接收方共同定义的声明。

    ​ payload的示例如下:

    { "sub": "1234567890", "name": "John Doe", "admin": true }

    ​ 通过Base64对上述JSON编码后的结果为eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ,并作为JWT的第二部分。

    3、signature(签名)

    ​ 签名是令牌的第三部分,由header和payload进行64位编码后再使用加密算法加密即可,示例如下:

    HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret);//secret为自定义的密码,如进行SHA256加密后的密码

    ​ 下面为完整的JWT示例:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    ​ 可在JWT官网(https://jwt.io/#debugger-io)中进行数据的解析,如下图是解析的结果:

    JWT的使用

    1、NETCore控制台中使用JWT

    ​ 具体代码如下:

    using JWT.Algorithms; using JWT.Builder; using JWT.Exceptions; using JWT.Serializers; using System; using System.Collections.Generic; namespace JWT.Api { class Program { //将"JWT"三个字母通过SHA256加密后得到 private const string secret = "fc93cb07e1ad92898527100e58a1cf1d1e7f65e9a266a6f87f3c84feb541c7b3"; static void Main(string[] args) { JWTEncode();//获取JWT 方式一 JWTEncode_2(); //获取JWT 方式二 Console.ReadKey(); } /// <summary> /// 获取JWT 方式一 /// </summary> public static void JWTEncode() { ///组成jwt的header //var header = new Dictionary<string, object> //{ // { "alg","HS256"}, // { "typ", "JWT" }, //}; //定义payload中的数据 里面的数据可随意填,一般都是返回用户数据 var payload = new Dictionary<string, object> { { "name", "张三" }, { "time", DateTime.Now } }; //加密的秘钥,这个接收端也需要有相同的 “jwt”字符串进行SH256加密 //生成JWT签名的算法 IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //JSON序列化与反序列化的接口 IJsonSerializer serializer = new JsonNetSerializer(); //Base64编码器 IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); //JWT编码器 IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); // extraHeaders: // 任意一组额外的标题。即自定义使用的签名算法和加密类型 // payload: // 任意负载(必须可序列化为JSON) // key: // 用于签名令牌的密钥 //不加header表示使用默认的签名算法和加密类型 var token = encoder.Encode(payload,secret); Console.WriteLine($"方式一生成的token为:[{token}]"); JWTDecode(token);//通过第一种方式进行解码 } /// <summary> /// 获取JWT 方式二 /// </summary> public static void JWTEncode_2() { //使用Fluent API对JWT进行编译。 var token = new JwtBuilder() .WithAlgorithm(new HMACSHA256Algorithm()) // 设置JWT算法 .WithSecret(secret)//设置加密密钥 .AddClaim("time",DateTime.Now)//设置时间 .AddClaim("name", "李四") .Encode();//使用提供的依赖项对令牌进行编码 Console.WriteLine($"方式二生成的token为:[{token}]"); JWTDecode_2(token); } /// <summary> /// 解析JWT 方式一 /// </summary> public static void JWTDecode(string token) { try { //JSON序列化与反序列化的接口 IJsonSerializer serializer = new JsonNetSerializer(); //UTC日期时间的提供程序。 var provider = new UtcDateTimeProvider(); //给定JWT,在不引发异常的情况下验证其签名的正确性 IJwtValidator validator = new JwtValidator(serializer, provider); //Base64编码器 IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); //对称 JWT签名的算法 IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //JWT解码器 IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); //解析后的json //token 要解析的token //secret 解析token需要的秘钥 //verify 是否验证签名(默认为true) var json = decoder.Decode(token, secret, verify: true); //输出解析后的json Console.WriteLine($"方式一解析后的json为:[{json}]"); } catch (TokenExpiredException ex) { Console.WriteLine("令牌已过期:"+ex.ToString()); } catch (SignatureVerificationException ex) { Console.WriteLine("签名有误:"+ex.ToString()); } catch (Exception ex) { Console.WriteLine("解析json时出现异常:" + ex.ToString()); } } /// <summary> /// 解析JWT 方式二 /// </summary> public static void JWTDecode_2(string token) { try { var json = new JwtBuilder() .WithAlgorithm(new HMACSHA256Algorithm()) // 设置JWT算法 .WithSecret(secret)//校验的秘钥 .MustVerifySignature()//必须校验秘钥 .Decode(token);//解析token //输出解析后的json Console.WriteLine($"方式二解析后的json为:[{json}]"); } catch (Exception ex) { Console.WriteLine("解析json时出现异常:"+ ex.ToString()); } } } }
    2、NETCore Web中使用JWT

    ​ (1)创建一个Web Api程序(步骤省略。。。)

    ​ (2)创建一个JWTService类

    public interface IJWTService { /// <summary> /// 根据用户名获取token /// </summary> /// <param name="UserName"></param> /// <returns></returns> string GetToken(string UserName); } /// <summary> /// 生成JWT的service类 /// </summary> public class JWTService : IJWTService { private readonly IConfiguration _configuration; /// <summary> /// 在构造函数中注入configuration以拿取appsettings.json中的内容 /// </summary> /// <param name="configuration"></param> public JWTService(IConfiguration configuration) { this._configuration = configuration; } /// <summary> /// 根据用户名获取token /// </summary> /// <param name="UserName"></param> /// <returns></returns> public string GetToken(string UserName) { //注:下面调用方法都是使用了默认的header //初始化payload Claim[] claims = new[] { new Claim(ClaimTypes.Name,UserName), new Claim("name","zhangsan"), new Claim("time",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")) }; //生成对称秘钥 SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["secret"])); //初始化签名凭证 SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); /** * Claims (Payload) Claims 部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,下面节选一些字段: iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的. iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。 除了规定的字段外,可以包含其他任何 JSON 兼容的字段。 * */ var token = new JwtSecurityToken( issuer: _configuration["issuer"],//设置签发者 audience: _configuration["audience"],//设置接收者 claims: claims,//设置payload expires: DateTime.Now.AddMinutes(5),//5分钟有效期 signingCredentials: creds);//初始化安全令牌参数 //输出token string returnToken = new JwtSecurityTokenHandler().WriteToken(token); return returnToken; } }

    ​ (3)appsetting.json中代码如下:

    { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", //JWT start "audience": "zhagnsan", "issuer": "lisi", "secret": "fc93cb07e1ad92898527100e58a1cf1d1e7f65e9a266a6f87f3c84feb541c7b3" //JWT end }

    ​ (4)Start.up中代码如下:

    public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddScoped<IJWTService, JWTService>();//将JWTService注入,那样就可以在构造函数中进行注入。如果没加进来,在构造函数中注入configuration是无用的 services.AddControllers(); #region JWT鉴权注入 //1.Nuget引入程序包:Microsoft.AspNetCore.Authentication.JwtBearer var ValidAudience = this.Configuration["audience"]; var ValidIssuer = this.Configuration["issuer"]; var SecurityKey = this.Configuration["secret"]; services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //默认授权机制名称 .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true,//是否在令牌期间验证签发者 ValidateAudience = true,//是否验证接收者 ValidateLifetime = true,//是否验证失效时间 ValidateIssuerSigningKey = true,//是否验证签名 ValidAudience = ValidAudience,//接收者 ValidIssuer = ValidIssuer,//签发者,签发的Token的人 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityKey))//拿到SHA256加密后的key }; }); #endregion } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthorization();//使用Authorization app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } }

    (5)AuthenticationController中代码如下:

    using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AuthenticationCenter.Utils; using JWT.Algorithms; using JWT.Builder; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace AuthenticationCenter.Controllers { [Route("api/[controller]")] [ApiController] public class AuthenticationController : ControllerBase { #region 注入 private IJWTService _iJWTService = null;//注入IJWTService接口 private IConfiguration _configuration = null;//注入配置信息接口 /// <summary> /// 构造器中进行注入 /// </summary> /// <param name="logger"></param> /// <param name="iJWTService"></param> /// <param name="configuration"></param> public AuthenticationController( IJWTService iJWTService, IConfiguration configuration) { this._iJWTService = iJWTService; this._configuration = configuration; } #endregion /// <summary> /// 请求token /// </summary> /// <param name="name"></param> /// <returns></returns> [Route("RequestToken")] [HttpGet] public string RequestToken(string name) { //如果等于admin那么就调用方法生成token,这里测试所以写死了 if ("admin".Equals(name)) { string token = this._iJWTService.GetToken(name); return JsonConvert.SerializeObject(new { result = true, token }); } else { return JsonConvert.SerializeObject(new { result = false, token = "无法请求" }); } } /// <summary> /// 校验token并返回 /// </summary> /// <returns></returns> [HttpGet] [Route("CheckAuthorize")] // [Authorize] //Microsoft.AspNetCore.Authorization public IActionResult CheckAuthorize() { try { //获取claims var claims = base.HttpContext.AuthenticateAsync().Result.Principal.Claims.ToList(); //获取请求的token var token = base.HttpContext.AuthenticateAsync().Result.Properties.Items.ToArray()[0].Value; string json = JWTDecode(token);//解析token return new JsonResult(new { Data = "已授权", Type = "CheckAuthorize", Claim = claims[0].Issuer, Json = json }); } catch (Exception ex) { return new JsonResult(new { Data = "未授权", Type = "CheckAuthorize", Exception = ex.ToString() }); } } /// <summary> /// 解析JWT /// </summary> /// <param name="token"></param> public string JWTDecode(string token) { try { var json = new JwtBuilder() .WithAlgorithm(new HMACSHA256Algorithm()) // 设置JWT算法 .WithSecret(_configuration["secret"])//校验的秘钥 .MustVerifySignature()//必须校验秘钥 .Decode(token);//解析token //输出解析后的json Console.WriteLine($"方式二解析后的json为:[{json}]"); return json; } catch (Exception ex) { Console.WriteLine("解析json时出现异常:" + ex.ToString()); return ""; } } } }

    (6)获取token

    (7)解析token,需要选择Authorization,Type选择Bearer Token

    参考资料:

    ​ (1)JWT官网https://jwt.io/

    ​ (2)JWT文档及源码:https://github.com/jwt-dotnet/jwt#JwtNet-ASPNET-Core

    ​ (3)B站朝夕视频:https://www.bilibili.com/video/BV1D7411y7Po

    项目源码:https://github.com/xgysigned/NETCoreProject

    Processed: 0.010, SQL: 10