JSON Web Token(简称JWT),是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。
授权:适用于单点登录。例:当用户登录成功时,服务器会返回一个token给当前登录用户,且用户登录后访问系统的各个模块都应携带该token进行请求,当token过期时,则不允许用户访问系统模块。
信息交换:jwt是各端(客户端、服务器端)之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),发送方与接收方可通过约定好的加密秘钥进行数据的解析。
JWT以紧凑的形式由三部分组成,这些部分由点(.
)分隔,分别是:header(头部)、payload(有效载荷)和signature(签名),即结构为header.payload.signature。
头部是令牌的第一部分,通常由两部分组成:令牌的类型(即JWT)和令牌所使用的签名算法,如SHA256、HMAC等。header示例如下:
{
"alg": "HS256",
"typ": "JWT"
}
通过Base64对上述JSON编码后的结果为eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9,并作为JWT的第一部分。
有效载荷是令牌的第二部分,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。主要有以下三种类型: 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的第二部分。
签名是令牌的第三部分,由header和payload进行64位编码后再使用加密算法加密即可,示例如下:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret);//secret为自定义的密码,如进行SHA256加密后的密码
下面为完整的JWT示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
可在JWT官网(https://jwt.io/#debugger-io)中进行数据的解析,如下图是解析的结果:
具体代码如下:
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();
}
///
/// 获取JWT 方式一
///
public static void JWTEncode()
{
///组成jwt的header
//var header = new Dictionary
//{
// { "alg","HS256"},
// { "typ", "JWT" },
//};
//定义payload中的数据 里面的数据可随意填,一般都是返回用户数据
var payload = new Dictionary
{
{ "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);//通过第一种方式进行解码
}
///
/// 获取JWT 方式二
///
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);
}
///
/// 解析JWT 方式一
///
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());
}
}
///
/// 解析JWT 方式二
///
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());
}
}
}
}
(1)创建一个Web Api程序(步骤省略。。。)
(2)创建一个JWTService类
public interface IJWTService
{
///
/// 根据用户名获取token
///
///
///
string GetToken(string UserName);
}
///
/// 生成JWT的service类
///
public class JWTService : IJWTService
{
private readonly IConfiguration _configuration;
///
/// 在构造函数中注入configuration以拿取appsettings.json中的内容
///
///
public JWTService(IConfiguration configuration)
{
this._configuration = configuration;
}
///
/// 根据用户名获取token
///
///
///
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();//将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;//注入配置信息接口
///
/// 构造器中进行注入
///
///
///
///
public AuthenticationController( IJWTService iJWTService, IConfiguration configuration)
{
this._iJWTService = iJWTService;
this._configuration = configuration;
}
#endregion
///
/// 请求token
///
///
///
[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 = "无法请求"
});
}
}
///
/// 校验token并返回
///
///
[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()
});
}
}
///
/// 解析JWT
///
///
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