1.概括
从原理上来说jwt实现的身份认证,实际上就是加密解密的过程放到http请求中的一个过程,服务端将特定信息加密,编码后返回给客户端,客户端在请求的过程中将这段经过加密编码的信息放到请求头中,服务端在收到请求的时候,根据头信息特定字段来判断该请求的来源是否是本站点的正常请求,所对应的系统用户等.所以从这个角度看jwt是分为两部分的,第一部分是加密编码生成串,第二部分就是验证.在实际工程中一般也会涉及到续期,什么时机要对客户端的jwt串传出即将过期指令,什么时机客户端用新的jwt串来执行新请求,是一个工程问题,解决方式不只一种,如何做取决于实际情况,当然还会涉及到token过期,信息安全等等一系列问题,本文将针对jwt使用过程中的所有问题展开讨论,给出一个安全可靠的jwt实践方案.
2.jwt是什么
RFC 7519 - JSON Web Token (JWT)
JSON Web Token (JWT) 是一种紧凑的、URL 安全的表示方式
要求在两方之间转让。JWT 中的声明
被编码为 JSON 对象,用作 JSON 的有效负载
Web 签名 (JWS) 结构或作为 JSON Web 的明文
加密 (JWE) 结构,使声明能够数字化
使用消息验证码进行签名或完整性保护
(MAC) 和/或加密。
jwt 的定义如上, JSON Web Tokens - jwt.io中有一个在线的jwt调试器,后边内容有多处会用到jwt调试器来验证我们的jwt实现.
3.基础实现
1.创建项目
mkdir Hero
cd Hero
mkdir Hero.Api
mkdir Hero.Jwt
cd Hero.Api
dotnet new webapi -f net5.0
cd ../Hero.Jwt
dotnet new classlib -f net5.0
cd ../
dotnet new sln
dotnet sln Hero.sln add Hero.Api/Hero.Api.csproj
dotnet sln Hero.sln add Hero.Jwt/Hero.Jwt.csproj
2.定义jwt结构中需要用到的信息,放到一个类中
首先我们知道jwt中一定会有的字段有Issuer,Audience,另外jwt有过期时间,所以要有代表生命周期的Lifetime,表示续期的RenewalTime,然后是头字段,是否验证失效时间,是否验证签名等,于是就有了如下结构. JwtConfig.cs
在Hero.Jwt中定义一个叫做JwtConfig的类,表示针对jwt的所有配置信息
internal class JwtConfig
{
public string Issuer { get; set; }
public string Audience { get; set; }
/// <summary>
/// 签名key
/// </summary>
public string SecretKey { get; set; }
/// <summary>
/// 生命周期
/// </summary>
public int Lifetime { get; set; }
/// <summary>
/// 续期时间
/// </summary>
public int RenewalTime { get; set; }
/// <summary>
/// 是否验证生命周期
/// </summary>
public bool ValidateLifetime { get; set; }
/// <summary>
/// 验证头字段
/// </summary>
public string HeadField { get; set; }
/// <summary>
/// 新Token的Head字段
/// </summary>
public string ReTokenHeadField { get; set; }
/// <summary>
/// jwt验证前缀
/// </summary>
public string Prefix { get; set; }
/// <summary>
/// 忽略验证的url
/// </summary>
public List<string> IgnoreUrls { get; set; }
}
有关jwt的方法应该也只有两个,一个是根据一些信息Claims获取token,另外一个就是验证token是否有效,如果有效返回jwt中的自定义Claims.紧接着定义一个IJwt的接口定义这两个方法
public interface IJwt
{
/// <summary>
/// 生成Token
/// </summary>
/// <param name="Claims">需要传递到jwt中的自定义信息</param>
/// <returns></returns>
string GetToken(IDictionary<string, object> Claims);
/// <summary>
/// 验证Token
/// </summary>
/// <param name="Token">待验证的字符串</param>
/// <param name="Claims">得到传入Claims信息</param>
/// <returns></returns>
bool ValidateToken(string Token, out Dictionary<string, object> Claims);
}
3.裸奔式的实现
首先是实现IJwt
要用到configration中的一些东西 和 jwt验证所以引入下边这三个类库 ,也是jwt的所有依赖
工具->nuge包管理器->程序包管理器控制台
install-package Microsoft.Extensions.Configuration.Abstractions //config
install-package Microsoft.Extensions.Configuration.Binder //config
install-package Microsoft.AspNetCore.Http.Abstractions//中间件中用到
install-package System.IdentityModel.Tokens.Jwt //jwt底层库
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
namespace Hero.Jwt
{
public class Jwt : IJwt
{
private readonly JwtConfig _jwtConfig = new JwtConfig();
private byte[] signingKey;
public Jwt(IConfiguration configration)
{
configration.GetSection("Jwt").Bind(_jwtConfig);
GenrateSigningKey();
}
/// <summary>
/// 生成签名
/// 需要用到byte[64]位固定长度签名,所以这里用签名算法做是最为稳妥的.
/// </summary>
private void GenrateSigningKey()
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(_jwtConfig.SecretKey);
byte[] messageBytes = encoding.GetBytes(_jwtConfig.SecretKey);
using (HMACSHA512 hmacsha512 = new HMACSHA512(keyByte))
{
signingKey = hmacsha512.ComputeHash(messageBytes);
}
}
/// <summary>
/// 生成Token
/// </summary>
/// <param name="Claims"></param>
/// <returns></returns>
public string GetToken(IDictionary<string, object> Claims)
{
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _jwtConfig.Issuer,
Audience = _jwtConfig.Audience,
NotBefore = DateTime.Now,
Expires = DateTime.Now.AddMinutes(_jwtConfig.Lifetime),
Claims =Claims,
//不同的签名算法要求的key长度是不同的,根据算法为SymmetricSecurityKey传参
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(signingKey),
SecurityAlgorithms.HmacSha512Signature)
};
SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(securityToken);
}
public bool ValidateToken(string Token, out Dictionary<string, object> Clims)
{
Clims = new Dictionary<string, object>();
ClaimsPrincipal principal = null;
if (string.IsNullOrWhiteSpace(Token))
{
return false;
}
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
try
{
JwtSecurityToken jwt = handler.ReadJwtToken(Token);
if (jwt == null)
{
return false;
}
TokenValidationParameters validationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero,
IssuerSigningKey = new SymmetricSecurityKey(signingKey),//签名验证算法
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = _jwtConfig.ValidateLifetime,//是否验证失效时间
ValidAudience = _jwtConfig.Audience,
ValidIssuer = _jwtConfig.Issuer,
ValidateIssuerSigningKey = true,//是否验证签名
};
principal = handler.ValidateToken(Token, validationParameters, out SecurityToken securityToken);
foreach (Claim item in principal.Claims)
{
Clims.Add(item.Type, item.Value);
}
return true;
}
catch (SecurityTokenInvalidLifetimeException ex)
{
Console.WriteLine(ex.Message);
return false;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
}
}
紧接着增加一个中间件,用于判断请求头中的携带jwt的字段,在token即将过期时发放新Token
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Hero.Jwt
{
public class UseJwtMiddleware
{
private readonly RequestDelegate _next;
private readonly JwtConfig _jwtConfig = new JwtConfig();
private readonly IJwt _jwt;
public UseJwtMiddleware(RequestDelegate next, IConfiguration configration, IJwt jwt)
{
_next = next;
_jwt = jwt;
configration.GetSection("Jwt").Bind(_jwtConfig);
}
private Regex GetIgnoreUrlsReg(List<string> urls)
{
List<string> regStrs = new List<string>();
foreach (var item in urls)
{
regStrs.Add(string.Concat("^", item, ".*$"));
}
return new Regex(string.Join("|", regStrs));
}
public Task InvokeAsync(HttpContext context)
{
if (GetIgnoreUrlsReg(_jwtConfig.IgnoreUrls).Match(context.Request.Path).Success)
{
return Validate(context, false);
}
else
{
return Validate(context, true);
}
}
private Task Validate(HttpContext context, bool ValidateToken)
{
//验证token的情况下执行
if (ValidateToken && context.Request.Headers.TryGetValue(this._jwtConfig.HeadField, out Microsoft.Extensions.Primitives.StringValues authValue))
{
var authstr = authValue.ToString();
if (this._jwtConfig.Prefix.Length > 0)
{
if (!authstr.Contains(this._jwtConfig.Prefix))
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync("{\"Status\":401,\"StatusMsg\":\"权限验证失败\"}");
}
authstr = authValue.ToString().Substring(this._jwtConfig.Prefix.Length, authValue.ToString().Length - (this._jwtConfig.Prefix.Length));
}
if (this._jwt.ValidateToken(authstr, out Dictionary<string, object> Clims))
{
List<string> climsKeys = new List<string>() { "nbf", "exp", "iat", "iss", "aud" };
IDictionary<string, object> RenewalDic = new Dictionary<string, object>();
foreach (var item in Clims)
{
if (climsKeys.FirstOrDefault(o => o == item.Key) == null)
{
if (context.Items.Keys.Contains(item.Key))//如果存在key则清理key;
{
context.Items.Remove(item.Key);
}
//将所有自定义的Claims信息写入httpcontext上下文中
context.Items.Add(item.Key, item.Value);
RenewalDic.Add(item.Key, item.Value);
}
}
//验证通过的情况下判断续期时间
if (Clims.Keys.FirstOrDefault(o => o == "exp") != null)
{
var start = new DateTime(1970, 1, 1, 0, 0, 0);
var timespanStart = long.Parse(Clims["nbf"].ToString());//token有效时间的开始时间点
var tartDate = start.AddSeconds(timespanStart).ToLocalTime();
var o = DateTime.Now - tartDate;//当前时间减去开始时间大于续期时间限制
if (o.TotalMinutes > _jwtConfig.RenewalTime)
{
//执行续期
var newToken = this._jwt.GetToken(RenewalDic);
context.Response.Headers.Add(_jwtConfig.ReTokenHeadField, newToken);
}
}
return this._next(context);
}
else
{
if (ValidateToken == true)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync("{\"Status\":401,\"StatusMsg\":\"权限验证失败\"}");
}
else
{
return this._next(context);
}
}
}
else
{
if (ValidateToken == true)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync("{\"Status\":401,\"StatusMsg\":\"权限验证失败\"}");
}
else
{
return this._next(context);
}
}
}
}
}
如上在中间件中定义了需要忽略jwt认证的url通过正则表达式做的匹配.对于api项目而言,这种处理方式避免了每个controller上都加特性,相对来说还是比较实用的,在续期时间间隔内,再次用旧Token执行请求就会在响应中增加该会话合法的新Token,客户端看到Head中有特定字段 ReToken 的时候,新请求的Token执行替换就可以了,Token完全过期后或者Token错误返回401错误
紧接着在api项目的appsettings.json中增加jwt的配置节点
"Jwt": {
"Issuer": "HeroIssuer",
"Audience": "HeroAudience",
"SecretKey": "E5807DDD-F481-444C-B3F7-2DBF5AB5043F",
"Lifetime": 1440, //单位分钟
"RenewalTime": 1, //单位分钟,Token续期的时间间隔,10表示超过10分钟再次请求就续期
"ValidateLifetime": true,
"HeadField": "Token", //头字段
"ReTokenHeadField": "ReToken",
"Prefix": "hero ", //前缀
"IgnoreUrls": [ //最终会拼接为 ^url1.*$|url2.*$|url3.*$,对请求执行匹配,成功的执行过滤
"/swagger/",
"/Auth/GetToken"//获取token
]
}
api项目增加对Hero.Jwt的引用,然后在startup.cs中将中间件注册一下,顺便改一下swagger的配置,以适用jwt
如下:
using System;
using Hero.Jwt;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
namespace Hero.Api
{
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.AddTransient<IJwt, Hero.Jwt.Jwt>();//注入IJwt
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("hero", new OpenApiSecurityScheme()
{
In = ParameterLocation.Header,
Name = "Token",
Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入hero {token}(注意两者之间是一个空格)",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{ new OpenApiSecurityScheme
{
Reference = new OpenApiReference()
{
Id = "hero",
Type = ReferenceType.SecurityScheme
}
}, Array.Empty<string>() }
});
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Hero.Api", Version = "v1" });
});
}
// 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.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Hero.Api v1"));
}
app.UseRouting();
app.UseJwt();//注册中间件
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Controller文件夹中增加用于发放Token的接口和测试接口..
using Hero.Jwt;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace Hero.Api.Controllers
{
[Route("[controller]/[action]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IJwt _jwt;
public AuthController(IJwt jwt)
{
_jwt = jwt;
}
[HttpPost]
public string GetToken(UserInfo userInfo)
{
//TODO 用户逻辑判断
Dictionary<string, object> clims = new Dictionary<string, object>
{
{"UserName", userInfo.UserName }
};
var result = _jwt.GetToken(clims);
return result;
}
}
}
验证jwt的接口
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Hero.Api.Controllers
{
[Route("[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
public string GetString()
{
return $"有Token才能请求到信息 这是jwt中写入到httpcontext中的内容:{HttpContext.Items["UserName"]}";
}
}
}
4.信息安全版jwt实现
基础版中的实现里有对jwt来源签名的验签过程,但是放在Claims中的信息并不安全,打开jwt.io,输入请求中拿到的jwt串
这里 可以很清楚的看到我们Claims的信息,这样的话,只要别人知道签名的key就能伪造签名,即便说一般获取不了签名,别人可以看到系统中的一些机密信息也是很不好的体验.下边就这个问题,用对称加密和非对称加密两种方式对信息进行加密.
1.对称加密+签名
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
namespace Hero.Jwt
{
public class Jwt : IJwt
{
private readonly JwtConfig _jwtConfig = new JwtConfig();
private byte[] signingKey;
private byte[] encKey=new byte[32];
public Jwt(IConfiguration configration)
{
configration.GetSection("Jwt").Bind(_jwtConfig);
GenrateSigningKey();
}
/// <summary>
/// 生成签名
/// 需要用到512位固定长度签名,所以这里用签名算法做是最为稳妥的.
/// </summary>
private void GenrateSigningKey()
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(_jwtConfig.SecretKey);
byte[] messageBytes = encoding.GetBytes(_jwtConfig.SecretKey);
using (HMACSHA512 hmacsha512 = new HMACSHA512(keyByte))
{
signingKey = hmacsha512.ComputeHash(messageBytes);
Buffer.BlockCopy(signingKey, 10, encKey, 0, encKey.Length);
}
}
/// <summary>
/// 生成Token
/// </summary>
/// <param name="Claims"></param>
/// <returns></returns>
public string GetToken(IDictionary<string, object> Claims)
{
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _jwtConfig.Issuer,
Audience = _jwtConfig.Audience,
NotBefore = DateTime.Now,
Expires = DateTime.Now.AddMinutes(_jwtConfig.Lifetime),
Claims =Claims,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(signingKey), SecurityAlgorithms.HmacSha512),//对称加密
EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encKey), SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)
};
SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(securityToken);
}
public bool ValidateToken(string Token, out Dictionary<string, object> Clims)
{
Clims = new Dictionary<string, object>();
ClaimsPrincipal principal = null;
if (string.IsNullOrWhiteSpace(Token))
{
return false;
}
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
try
{
JwtSecurityToken jwt = handler.ReadJwtToken(Token);
if (jwt == null)
{
return false;
}
TokenValidationParameters validationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero,
IssuerSigningKey = new SymmetricSecurityKey(signingKey),
TokenDecryptionKey = new SymmetricSecurityKey(encKey),
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = _jwtConfig.ValidateLifetime,//是否验证失效时间
ValidAudience = _jwtConfig.Audience,
ValidIssuer = _jwtConfig.Issuer,
ValidateIssuerSigningKey = true,//是否验证签名
};
principal = handler.ValidateToken(Token, validationParameters, out SecurityToken securityToken);
foreach (Claim item in principal.Claims)
{
Clims.Add(item.Type, item.Value);
}
return true;
}
catch (SecurityTokenInvalidLifetimeException ex)
{
Console.WriteLine(ex.Message);
return false;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
}
}
在发Token和验证的时候指定对称算法名称即可
2.非对称加密+签名
修改Jwt.cs
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
namespace Hero.Jwt
{
public class Jwt : IJwt
{
private readonly JwtConfig _jwtConfig = new JwtConfig();
private byte[] signingKey;
private byte[] encKey=new byte[32];
public Jwt(IConfiguration configration)
{
configration.GetSection("Jwt").Bind(_jwtConfig);
GenrateSigningKey();
}
/// <summary>
/// 生成签名
/// 需要用到512位固定长度签名,所以这里用签名算法做是最为稳妥的.
/// </summary>
private void GenrateSigningKey()
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(_jwtConfig.SecretKey);
byte[] messageBytes = encoding.GetBytes(_jwtConfig.SecretKey);
using (HMACSHA512 hmacsha512 = new HMACSHA512(keyByte))
{
signingKey = hmacsha512.ComputeHash(messageBytes);
Buffer.BlockCopy(signingKey, 10, encKey, 0, encKey.Length);
}
}
/// <summary>
/// 生成Token
/// </summary>
/// <param name="Claims"></param>
/// <returns></returns>
public string GetToken(IDictionary<string, object> Claims)
{
string pubkeySigning = "MIIBCgKCAQEAuyeviRRcsPGxSUyYnhE8zYFquUc4InAa5K4qw5Y43R/1sm5jWZiG670+aq+y/1Ovx4RXlyBpzwo4ws0FB7YZyPIPRiZZbB9oIeNd19fdPu7U70lSaIfh9TjHW9Kjb40YFnxm6nltY7QXTw2VeISVZApD0DcL8TieD3cgGpKJjo4nhaHNFmRLR80mSOjFrKFoun5iLk1azljHEpVjqVotKF+5o80DzxW7EywoS2YuXYMI7tOIPP33cRLMFxokZSy2BFeeuu4jakDsvTxMaipOCyumbE2/Fd7bo59n1/jgfyP11rR8HtkhdS9SbK0nw6rVEddFxym6TKjczrxIYxuR4QIDAQAB";
string prikeySigning = "MIIEogIBAAKCAQEAuyeviRRcsPGxSUyYnhE8zYFquUc4InAa5K4qw5Y43R/1sm5jWZiG670+aq+y/1Ovx4RXlyBpzwo4ws0FB7YZyPIPRiZZbB9oIeNd19fdPu7U70lSaIfh9TjHW9Kjb40YFnxm6nltY7QXTw2VeISVZApD0DcL8TieD3cgGpKJjo4nhaHNFmRLR80mSOjFrKFoun5iLk1azljHEpVjqVotKF+5o80DzxW7EywoS2YuXYMI7tOIPP33cRLMFxokZSy2BFeeuu4jakDsvTxMaipOCyumbE2/Fd7bo59n1/jgfyP11rR8HtkhdS9SbK0nw6rVEddFxym6TKjczrxIYxuR4QIDAQABAoIBAHAnCwjhW95pJ61eKkLm34HjIPpglGIGvgb13AiS+AaCxXCkuAKT5Z5VLJcwLNrW4op0YyzcLqv0WylZRL9nP7JsY/zMtF+XvoY4Qx86a4nwA0hVrv2XGDAkU0tSQcByU9H9wIqYM5ZA8IreAAlVolRt1k9q/UwTepyX7XQfBjGXNft0qu3A3gLIpzVn/oH5mD4UNsNDdgJ/s4FebvdGSucM/UWBoDP4TaotN1IV8yBNyFJhBWMkbRLs20dbfgiFqDnRF8+69Ylw6qrokHDZwZRB5/NMiFt+UFmCFc3RbSps2L1tWLOdAQ5MPTELn+1kk26jkcHP7cZXVbOzqHKQkTUCgYEA2LkujXIdOY1LeB5bSkPUND8PtmVgrsiAEfP/nWX629+UIqwwGtGJz9Gt5c3CNvIsGzazHaUE4DI5ru4B+HgU45ifbLMtII5AtdY8oPd0nTwHpF6iSYKmJzPFPdordyxZ36CLzhaVI9mdHRJS84Fj5O1DJ548PANLz5rTLDiJEbsCgYEA3RKwY636eo3sIjHVHTe3gOt+s3Q41P+Lh1v9TiEuZthg3f7tZUY2EQileLxNAjgAK48XMbtgRTLNax688WjjLIQaYQyXl074gJ1Hn/dirNOGQMrYgrxXmM5oBgXqmEA321W9hz9k7JRYw7HqjKSL2+h8GeacXfkFyC9YZiFFMxMCgYBdNhBSn6D4LtAlwpCq+U9chT7hyOpzYiLLFfF7pe/l/1w8KWirMDIgouMzMnL0pOXZcoZJGr9lGdT7aryIPEVnui3fV5TyKpykWJdM+AE82yPCSz1rdni15atQtfP51qZ06x0WL1pHyAGuDkKFHsJzJKS8dm8btKM3kDSBEXPKnwKBgG8bt39Br4Ps1GMTPJLkr9uhgBpdLTsP/GZZe2PLFXEnCvhH6bRep0nEWLXnnaSh1KQP1I5wKCBfOhK+biO+nX6AHmnsVDv9urOZWKgzQ2qtHOpviIWcd0Ibavir/I3sqKYZ35mb6PNmU353avSotoodvFGgL7KjN562/OzHh+n1AoGAFBFnvge50H2FTT2MlKZ53yY27OAuKK2+KUQGK8OIP4fSWvisFzA68cQrejyAxkx4JMMic0OnHgLMi75Noz6aUYDdtRmuzkmxugz96bhFuj1UhH3HqqaLwn8yrw68X4dUD6eSv7sBhL9RJTVz3cuaR1wFaXZfEhfVcdRlvDYfa0k=";
string pubkeyEncrypt = "MIIBCgKCAQEAxKrM4LqoQ7O37Wiybx/80NUwi9dDc+hP5MWx/uAyaoWsCTE3AXTcANqKR4MWd8FDKpyJn9WrIDaRnl4XuW8992Bpzqo4KbHlefQ6z848ox7M8BCqyiQ0uz5FeezFzhe1FBGpRmw1o1rH3mpjnkjpb81wWY6NyI6k7/ZUIQFX5Sn8g4vnaGNBlnhhd3oh8esM2IhoDcPvK2QXnpCa1o+md7O3VM1J1M2jIVvULaE5dxz2NIUCQnxFCgH/XYKfcEZEwjBubkFK2HP+pPKo0ZX9kfQ2MPOj7l04YTikA2Majym3BL38U/bNSt8rYHg5EIWwwObYxlo6NOWQRFYM9c1fZQIDAQAB";
string prikeyEncrypt = "MIIEpAIBAAKCAQEAxKrM4LqoQ7O37Wiybx/80NUwi9dDc+hP5MWx/uAyaoWsCTE3AXTcANqKR4MWd8FDKpyJn9WrIDaRnl4XuW8992Bpzqo4KbHlefQ6z848ox7M8BCqyiQ0uz5FeezFzhe1FBGpRmw1o1rH3mpjnkjpb81wWY6NyI6k7/ZUIQFX5Sn8g4vnaGNBlnhhd3oh8esM2IhoDcPvK2QXnpCa1o+md7O3VM1J1M2jIVvULaE5dxz2NIUCQnxFCgH/XYKfcEZEwjBubkFK2HP+pPKo0ZX9kfQ2MPOj7l04YTikA2Majym3BL38U/bNSt8rYHg5EIWwwObYxlo6NOWQRFYM9c1fZQIDAQABAoIBACr8vn2cryzlOp3NFbuOfV9USiE280qBi/0QbWCttrdr8ner5z8NQQ16t2D8OUwB1WGaB8cFGDuZUekQ3hStSRkqXNZMhKwwc11d0gEcLkrlb5xFuF8o3NHUwbDt3Sq4Kd9yINMA0hSbwjZOgOnXPBcxC463xywAafL9n9P7DDBNw17l2ykW7gJtNRy6tf2kjCEfnHEIgXZZ19xp/BfApYAcLb/v9C/tRaWAzWLLTwX9GEOrJ2XjzIUOrdCmdO85VbH3b2bGHWwi2orbD+uvCZOPcCsOyQjUme40JaDQwgkCPTh/9F5/u/Z8sVcCw/mgzoaxWImdCwwQCj49FhT52V0CgYEA/CEL1VlL5Gb2tolhG8hufORzu2Id0uDNKsUZd92hM4Y98tyDnCqE/F3jiv63G0utHSzC3Rt77iiX9LXXV3AP/Zz91LNEm/vICSEeDOFejx7XEsnjsJuoDMZPt+bNu4bFWv0j81fuVyZ9ZPoy4xb+abmkebq9F2QxvcQs2DO+SKMCgYEAx6/FEaGKk10XWCdFEBeI3I3GViDYzSBEEnaI+wSR7VPBZkCt0w2aD4/wrw3pmlTAkN882GDdFzojtEd7t2nXgp03S0F5T5aGD4pFZWaqUYqz/bQ/UxBp0C5qHR084J5XarllTPBzbZ0fo1eY1Cde/mj/TD722o+9hUsmbi0NkFcCgYEAsZRY8FCvmlRG6jQCeH4IC+Ef/lfR56g7+SbPlFQ+aLrhQP+9lq1/8vvx+wECWLBJYqYXLYJhHFHtDQdSf5xHNvpu8XO+HBsPPhbcQngtkKJJG0ulGcvYZf77QOzH9I+syzRGMOu6zBko8okidD3KvQ5q4O38ptAEFMNqTnDLUf8CgYB6k/VfG1DboRuBa6nDdQ74hLcpi8RKNvJSex0fKfECRJXF1RJfKkxWHT/b1ah+qmQDCmZpVRyi83eTZQYW0wwOC8AznB+BsZ7dzz1GP71xjLlslccBkGPD/Zn6AUarg8eZpfD/R+MzeG5BcLZKFVkExyNghI44IGBwgG841sMqxQKBgQD6nSV5kw8RXBlzWfjI/q9hU0f9U4E2ZX66iNrUhlmK12yDXd0l3I4vpffo2HQVHRGP+4LKnX7g+7P6Zrs3GHoOg7EbnYjcReoBNoxwgTx93b6qBEi9yfQNYVPgZIwxMYq62yNlTcAp9CQLAwkOWaTO51viAVICUOmbQelIny9cMA==";
RSA rsaSigning = RSA.Create();
//rsaSigning.ExportRSAPrivateKey() 生成私钥
//rsaSigning.ExportRSAPublicKey() 生成公钥
rsaSigning.ImportRSAPrivateKey(Convert.FromBase64String(prikeySigning), out int bytesread);
RSA rsaEncrypt = RSA.Create();
rsaEncrypt.ImportRSAPublicKey(Convert.FromBase64String(pubkeyEncrypt), out int bytesread1);
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _jwtConfig.Issuer,
Audience = _jwtConfig.Audience,
NotBefore = DateTime.Now,
Expires = DateTime.Now.AddMinutes(_jwtConfig.Lifetime),
Claims =Claims,
//SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(signingKey), SecurityAlgorithms.HmacSha512),//对称加密
//EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encKey), SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)
SigningCredentials = new SigningCredentials(new RsaSecurityKey(rsaSigning.ExportParameters(true)), SecurityAlgorithms.RsaSsaPssSha512),//非对称私钥签名,公钥验签
EncryptingCredentials = new EncryptingCredentials(new RsaSecurityKey(rsaEncrypt.ExportParameters(false)), SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes128CbcHmacSha256)//非对称加密
};
SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(securityToken);
}
public bool ValidateToken(string Token, out Dictionary<string, object> Clims)
{
string pubkeySigning = "MIIBCgKCAQEAuyeviRRcsPGxSUyYnhE8zYFquUc4InAa5K4qw5Y43R/1sm5jWZiG670+aq+y/1Ovx4RXlyBpzwo4ws0FB7YZyPIPRiZZbB9oIeNd19fdPu7U70lSaIfh9TjHW9Kjb40YFnxm6nltY7QXTw2VeISVZApD0DcL8TieD3cgGpKJjo4nhaHNFmRLR80mSOjFrKFoun5iLk1azljHEpVjqVotKF+5o80DzxW7EywoS2YuXYMI7tOIPP33cRLMFxokZSy2BFeeuu4jakDsvTxMaipOCyumbE2/Fd7bo59n1/jgfyP11rR8HtkhdS9SbK0nw6rVEddFxym6TKjczrxIYxuR4QIDAQAB";
string prikeySigning = "MIIEogIBAAKCAQEAuyeviRRcsPGxSUyYnhE8zYFquUc4InAa5K4qw5Y43R/1sm5jWZiG670+aq+y/1Ovx4RXlyBpzwo4ws0FB7YZyPIPRiZZbB9oIeNd19fdPu7U70lSaIfh9TjHW9Kjb40YFnxm6nltY7QXTw2VeISVZApD0DcL8TieD3cgGpKJjo4nhaHNFmRLR80mSOjFrKFoun5iLk1azljHEpVjqVotKF+5o80DzxW7EywoS2YuXYMI7tOIPP33cRLMFxokZSy2BFeeuu4jakDsvTxMaipOCyumbE2/Fd7bo59n1/jgfyP11rR8HtkhdS9SbK0nw6rVEddFxym6TKjczrxIYxuR4QIDAQABAoIBAHAnCwjhW95pJ61eKkLm34HjIPpglGIGvgb13AiS+AaCxXCkuAKT5Z5VLJcwLNrW4op0YyzcLqv0WylZRL9nP7JsY/zMtF+XvoY4Qx86a4nwA0hVrv2XGDAkU0tSQcByU9H9wIqYM5ZA8IreAAlVolRt1k9q/UwTepyX7XQfBjGXNft0qu3A3gLIpzVn/oH5mD4UNsNDdgJ/s4FebvdGSucM/UWBoDP4TaotN1IV8yBNyFJhBWMkbRLs20dbfgiFqDnRF8+69Ylw6qrokHDZwZRB5/NMiFt+UFmCFc3RbSps2L1tWLOdAQ5MPTELn+1kk26jkcHP7cZXVbOzqHKQkTUCgYEA2LkujXIdOY1LeB5bSkPUND8PtmVgrsiAEfP/nWX629+UIqwwGtGJz9Gt5c3CNvIsGzazHaUE4DI5ru4B+HgU45ifbLMtII5AtdY8oPd0nTwHpF6iSYKmJzPFPdordyxZ36CLzhaVI9mdHRJS84Fj5O1DJ548PANLz5rTLDiJEbsCgYEA3RKwY636eo3sIjHVHTe3gOt+s3Q41P+Lh1v9TiEuZthg3f7tZUY2EQileLxNAjgAK48XMbtgRTLNax688WjjLIQaYQyXl074gJ1Hn/dirNOGQMrYgrxXmM5oBgXqmEA321W9hz9k7JRYw7HqjKSL2+h8GeacXfkFyC9YZiFFMxMCgYBdNhBSn6D4LtAlwpCq+U9chT7hyOpzYiLLFfF7pe/l/1w8KWirMDIgouMzMnL0pOXZcoZJGr9lGdT7aryIPEVnui3fV5TyKpykWJdM+AE82yPCSz1rdni15atQtfP51qZ06x0WL1pHyAGuDkKFHsJzJKS8dm8btKM3kDSBEXPKnwKBgG8bt39Br4Ps1GMTPJLkr9uhgBpdLTsP/GZZe2PLFXEnCvhH6bRep0nEWLXnnaSh1KQP1I5wKCBfOhK+biO+nX6AHmnsVDv9urOZWKgzQ2qtHOpviIWcd0Ibavir/I3sqKYZ35mb6PNmU353avSotoodvFGgL7KjN562/OzHh+n1AoGAFBFnvge50H2FTT2MlKZ53yY27OAuKK2+KUQGK8OIP4fSWvisFzA68cQrejyAxkx4JMMic0OnHgLMi75Noz6aUYDdtRmuzkmxugz96bhFuj1UhH3HqqaLwn8yrw68X4dUD6eSv7sBhL9RJTVz3cuaR1wFaXZfEhfVcdRlvDYfa0k=";
string pubkeyEncrypt = "MIIBCgKCAQEAxKrM4LqoQ7O37Wiybx/80NUwi9dDc+hP5MWx/uAyaoWsCTE3AXTcANqKR4MWd8FDKpyJn9WrIDaRnl4XuW8992Bpzqo4KbHlefQ6z848ox7M8BCqyiQ0uz5FeezFzhe1FBGpRmw1o1rH3mpjnkjpb81wWY6NyI6k7/ZUIQFX5Sn8g4vnaGNBlnhhd3oh8esM2IhoDcPvK2QXnpCa1o+md7O3VM1J1M2jIVvULaE5dxz2NIUCQnxFCgH/XYKfcEZEwjBubkFK2HP+pPKo0ZX9kfQ2MPOj7l04YTikA2Majym3BL38U/bNSt8rYHg5EIWwwObYxlo6NOWQRFYM9c1fZQIDAQAB";
string prikeyEncrypt = "MIIEpAIBAAKCAQEAxKrM4LqoQ7O37Wiybx/80NUwi9dDc+hP5MWx/uAyaoWsCTE3AXTcANqKR4MWd8FDKpyJn9WrIDaRnl4XuW8992Bpzqo4KbHlefQ6z848ox7M8BCqyiQ0uz5FeezFzhe1FBGpRmw1o1rH3mpjnkjpb81wWY6NyI6k7/ZUIQFX5Sn8g4vnaGNBlnhhd3oh8esM2IhoDcPvK2QXnpCa1o+md7O3VM1J1M2jIVvULaE5dxz2NIUCQnxFCgH/XYKfcEZEwjBubkFK2HP+pPKo0ZX9kfQ2MPOj7l04YTikA2Majym3BL38U/bNSt8rYHg5EIWwwObYxlo6NOWQRFYM9c1fZQIDAQABAoIBACr8vn2cryzlOp3NFbuOfV9USiE280qBi/0QbWCttrdr8ner5z8NQQ16t2D8OUwB1WGaB8cFGDuZUekQ3hStSRkqXNZMhKwwc11d0gEcLkrlb5xFuF8o3NHUwbDt3Sq4Kd9yINMA0hSbwjZOgOnXPBcxC463xywAafL9n9P7DDBNw17l2ykW7gJtNRy6tf2kjCEfnHEIgXZZ19xp/BfApYAcLb/v9C/tRaWAzWLLTwX9GEOrJ2XjzIUOrdCmdO85VbH3b2bGHWwi2orbD+uvCZOPcCsOyQjUme40JaDQwgkCPTh/9F5/u/Z8sVcCw/mgzoaxWImdCwwQCj49FhT52V0CgYEA/CEL1VlL5Gb2tolhG8hufORzu2Id0uDNKsUZd92hM4Y98tyDnCqE/F3jiv63G0utHSzC3Rt77iiX9LXXV3AP/Zz91LNEm/vICSEeDOFejx7XEsnjsJuoDMZPt+bNu4bFWv0j81fuVyZ9ZPoy4xb+abmkebq9F2QxvcQs2DO+SKMCgYEAx6/FEaGKk10XWCdFEBeI3I3GViDYzSBEEnaI+wSR7VPBZkCt0w2aD4/wrw3pmlTAkN882GDdFzojtEd7t2nXgp03S0F5T5aGD4pFZWaqUYqz/bQ/UxBp0C5qHR084J5XarllTPBzbZ0fo1eY1Cde/mj/TD722o+9hUsmbi0NkFcCgYEAsZRY8FCvmlRG6jQCeH4IC+Ef/lfR56g7+SbPlFQ+aLrhQP+9lq1/8vvx+wECWLBJYqYXLYJhHFHtDQdSf5xHNvpu8XO+HBsPPhbcQngtkKJJG0ulGcvYZf77QOzH9I+syzRGMOu6zBko8okidD3KvQ5q4O38ptAEFMNqTnDLUf8CgYB6k/VfG1DboRuBa6nDdQ74hLcpi8RKNvJSex0fKfECRJXF1RJfKkxWHT/b1ah+qmQDCmZpVRyi83eTZQYW0wwOC8AznB+BsZ7dzz1GP71xjLlslccBkGPD/Zn6AUarg8eZpfD/R+MzeG5BcLZKFVkExyNghI44IGBwgG841sMqxQKBgQD6nSV5kw8RXBlzWfjI/q9hU0f9U4E2ZX66iNrUhlmK12yDXd0l3I4vpffo2HQVHRGP+4LKnX7g+7P6Zrs3GHoOg7EbnYjcReoBNoxwgTx93b6qBEi9yfQNYVPgZIwxMYq62yNlTcAp9CQLAwkOWaTO51viAVICUOmbQelIny9cMA==";
RSA rsaSigning = RSA.Create();
rsaSigning.ImportRSAPublicKey(Convert.FromBase64String(pubkeySigning), out int bytesread);
RSA rsaEncrypt = RSA.Create();
rsaEncrypt.ImportRSAPrivateKey(Convert.FromBase64String(prikeyEncrypt), out int bytesread1);
Clims = new Dictionary<string, object>();
ClaimsPrincipal principal = null;
if (string.IsNullOrWhiteSpace(Token))
{
return false;
}
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
try
{
JwtSecurityToken jwt = handler.ReadJwtToken(Token);
if (jwt == null)
{
return false;
}
TokenValidationParameters validationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero,
//IssuerSigningKey = new SymmetricSecurityKey(signingKey),
//TokenDecryptionKey = new SymmetricSecurityKey(encKey),
IssuerSigningKey = new RsaSecurityKey(rsaSigning.ExportParameters(false)),//公钥解密 --签名算法
TokenDecryptionKey = new RsaSecurityKey(rsaEncrypt.ExportParameters(true)),//私钥解密--解密算法
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = _jwtConfig.ValidateLifetime,//是否验证失效时间
ValidAudience = _jwtConfig.Audience,
ValidIssuer = _jwtConfig.Issuer,
ValidateIssuerSigningKey = true,//是否验证签名
};
principal = handler.ValidateToken(Token, validationParameters, out SecurityToken securityToken);
foreach (Claim item in principal.Claims)
{
Clims.Add(item.Type, item.Value);
}
return true;
}
catch (SecurityTokenInvalidLifetimeException ex)
{
Console.WriteLine(ex.Message);
return false;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
}
}
用非对称加密生成的jwt串更长,限制是被加密内容不能超出密钥长度,也就是Claims的大小有一定限制,一般情况应该是满足的.