赞
踩
我一起写后端Api我都是直接裸连的,但是现在为了规范一些,我还是打算上一个JWT功能
选好了模板,就进去看看号了,42M的下载量已经很高了,一般来说,只要超过500k的下载量,基本就是一个稳定的代码仓库了。
进去看看里面写的怎么样
可以看到写的还是比较清晰的
JWT全名 JSON Web Token。Token这部分才是加密得出来的,JWT只是一个网页认证策略。
无论是可逆加密还是不可逆加密,都需要一个自定义的【Key】来辅助加密。可逆加密就类似于一元二次方程,key就类似于修改方程各项的系数,【Key=125】得到【x^2+2x+5】。我的原文是【1】,得到的密文就是计算后的结果【8】。所以加密比解密要方便。
不可逆加密就是不能逆向解决的方程,比如高阶方程【x^7- x^3 +5x-3】,正向好算,逆向基本不可解。
所以现实使用的时候,可逆加密一般是解密后使用。因为可逆,所以可以用于复杂的敏感信息。
不可逆加密是判断加密后的的密文是否相同,比如密码,是不能泄漏的,所以我们的密码只能重置,因为系统也不知道原密码是什么。
我们先不管JWT是如何添加到ASP.NET Core里面的,我们先测试JWT的加密和登录验证的功能。
/// <summary> /// 加密密钥 /// </summary> private static readonly string jwtKey = "TokenKey"; public record UserTest(string Name,string Account,string Password); static void Main(string[] args) { UserTest userTest = new UserTest("小王","admin","1dixa0d"); Console.WriteLine("原文"); Console.WriteLine(JsonConvert.SerializeObject(userTest));//{"Name":"小王","Account":"admin","Password":"1dixa0d"} var encoder = GetEncoder(); string jwtToken = encoder.Encode(userTest,jwtKey); Console.WriteLine("加密"); Console.WriteLine(jwtToken);//eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJOYW1lIjoi5bCP546LIiwiQWNjb3VudCI6ImFkbWluIiwiUGFzc3dvcmQiOiIxZGl4YTBkIn0.C_tlDhHpkjAkJpRRdnqz6Jn2FmP0qONAI4oNl8Ye9wM var decoder = GetDecoder(); var decodeData = decoder.Decode(jwtToken,jwtKey); Console.WriteLine("解密"); Console.WriteLine(decodeData);//{"Name":"小王","Account":"admin","Password":"1dixa0d"} Console.WriteLine("Hello, World!"); } /// <summary> /// 获取加密解密 /// </summary> /// <returns></returns> public static IJwtEncoder GetEncoder() { IJwtAlgorithm algorithm = new HMACSHA256Algorithm();//加密方式 IJsonSerializer serializer = new JsonNetSerializer();//序列化Json IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密 IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);//JWT编码 return encoder; } /// <summary> /// 获取解密密钥 /// </summary> /// <returns></returns> public static IJwtDecoder GetDecoder() { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); return decoder; }
这里的JWT的加密解密算法是可以自己搭配的,选择加密算法,这里我就不展开了。详细可以看官方文档
首先我们之前用到了可逆的加密和解密。那我们就需要用一个判断是否过期的类
public class UserJwtLogin
{
/// <summary>
/// 用户Id,因为数据库的Id是唯一的,不会重复
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public DateTime ExpireTime { get; set; }
}
详细的解决方案就在这个文章里面了,我就不照抄了,拾人牙慧。
我这里是这么写的
新建一个JwtHelper
public static class JwtHelper { private static readonly string JwtKey = "这里填你的密钥"; /// <summary> /// 获取加密解密 /// </summary> /// <returns></returns> private static IJwtEncoder GetEncoder() { IJwtAlgorithm algorithm = new HMACSHA256Algorithm();//加密方式 IJsonSerializer serializer = new JsonNetSerializer();//序列化Json IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密 IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);//JWT编码 return encoder; } /// <summary> /// 获取解密密钥 /// </summary> /// <returns></returns> private static IJwtDecoder GetDecoder() { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); return decoder; } /// <summary> /// 加密 /// </summary> public static string Encode(object payload) { var encoder = GetEncoder(); var token = encoder.Encode(payload, JwtKey); return token; } /// <summary> /// 解密 /// </summary> public static T Decode<T>(string token) { var decoder = GetDecoder(); var data = decoder.Decode(token,JwtKey); var res = JsonConvert.DeserializeObject<T>(data); return res; } /// <summary> /// 解密,只返回Json文本 /// </summary> /// <param name="token"></param> /// <returns></returns> public static string Decode(string token) { var decoder = GetDecoder(); var data = decoder.Decode(token, JwtKey); return data; } }
两个实体类
public class UserJwtLogin
{
/// <summary>
/// 用户Id,因为数据库的Id是唯一的,不会重复
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public DateTime ExpireTime { get; set; }
}
public class WebMsg { public int Code { get; set; } = 200; public bool Success { get; set; } = true; public object Data {get; set; } public string Msg { get; set; } = "操作成功!"; public WebMsg() { } public WebMsg(object data) { Data = data; } }
/// <summary> /// Jwt登录 /// </summary> [Route("api/[controller]/[action]")] [ApiController] public class LoginController : ControllerBase { //这个是我的Nlog配置,这里不做展开 private NlogService nlogService; public LoginController(NlogService nlogService) { this.nlogService = nlogService; } /// <summary> /// JWT登录 /// </summary> /// <param name="username">账号</param> /// <param name="password">密码</param> /// <returns></returns> [HttpGet] public WebMsg Login(string username, string password) { if (username == null || password == null) { return new WebMsg() { Msg = "登录信息为空", Success = false, }; } if (username == "admin" && password == "123456") { //这里是模拟拿到数据库的User表的Id var pyload = new UserJwtLogin() { UserId = 291, ExpireTime = DateTime.Now.AddDays(1) }; var token = JwtHelper.Encode(pyload); return new WebMsg(token); } else { return new WebMsg() { Msg = "登录失败,账号或者密码错误", Success = false, }; } } /// <summary> /// Jwt解密 /// </summary> /// <param name="token"></param> /// <returns></returns> [HttpGet] public WebMsg Decode(string token) { try { var res = JwtHelper.Decode(token); return new WebMsg(res); } catch (Exception ex) { nlogService.Error("登录解析失败:" + token); nlogService.Error(ex.Message); return new WebMsg() { Msg = "登录解析失败:" + token, Success = false, }; } } }
{
"code": 200,
"success": true,
"data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjI5MSwiRXhwaXJlVGltZSI6IjIwMjQtMDMtMTRUMTM6MjE6MzYuNjE3NDk0KzA4OjAwIn0.sxh9sM4gQoCfFfim-MQSsHqDQX3Dji3FZaEu4t06D1s",
"msg": "操作成功!"
}
{
"code": 200,
"success": true,
"data": "{\"UserId\":291,\"ExpireTime\":\"2024-03-14T13:21:36.617494+08:00\"}",
"msg": "操作成功!"
}
这里我们就用简单的授权,复杂的授权可以自己去看微软官方文档
然后我发现.net core 8.0的认证方式好像变了,我按照博客的写法发现不让我重写了
这个方法是.net framework上面用的,.net core 用不了。我想还是算了,用.net core 的方法好了
我看了一天的博客,终于解决了JWT认证的问题。
这里我就不多说了
我们还是要改一下的,因为之前的我测试的时候,发现Builder里面的代码实在是太多了,这里我们分开一下
using Microsoft.OpenApi.Models; using System.Reflection; namespace JtwTestWebApi { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var MyPolicy = "MyPolicy"; // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //为了防止配套太多,代码混乱。这里自定义了一个方法 AddMyService(builder); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { } app.UseSwagger(); app.UseSwaggerUI(); app.UseStatusCodePagesWithRedirects("/swagger/index.html"); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); } /// <summary> /// 为了防止代码过于臃肿,将新的配置放在这里写 /// </summary> /// <param name="builder"></param> public static void AddMyService(WebApplicationBuilder builder) { builder.Services.AddCors(options => { options.AddPolicy("MyPolicy", policy => { policy.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod(); }); }); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", Title = "API标题", Description = $"API描述,v1版本" }); var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; //IncludeXmlComments 第二参数 true 则显示 控制器 注释 options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename), true); }); } } }
public class WebMsg { public object Data { get; set; } public bool Success { get; set; } = true; public string Msg { get; set; } = "操作成功!"; public WebMsg() { } public WebMsg(object data) { Data = data; } }
/// <summary> /// 测试控制器 /// </summary> [Route("api/[controller]/[action]")] [ApiController] public class TestController : ControllerBase { /// <summary> /// 测试返回值 /// </summary> /// <returns></returns> [HttpGet] public WebMsg GetTest() { return new WebMsg("测试返回值"); } }
我们知道Jwt其实就是生成和验证两个功能。微软把这个功能集成到一个JwtBearer库里面了。
为了方便Json打印,添加个Newtonsoft
我们在appsetting.json里面添加
"JwtConfig": {
"SecretKey": "123123123123", // 密钥 可以是guid 也可以是随便一个字符串
"Issuer": "XiaoWang", // 颁发者
"Audience": "XiaoWang", // 接收者
"Expired": 30 // 过期时间(30min)
}
public class JwtConfig { /// <summary> /// 密钥 /// </summary> public string SecretKey { get; set; } /// <summary> /// 发布者 /// </summary> public string Issuer { get; set; } /// <summary> /// 接受者 /// </summary> public string Audience { get; set; } /// <summary> /// 过期时间(min) /// </summary> public int Expired { get; set; } }
获取Config
var jwtConfig = new JwtConfig();
builder.Configuration.Bind("JwtConfig",jwtConfig);
public class JwtHelper { public JwtConfig JwtConfig { get; set; } public JwtHelper() { } /// <summary> /// 添加Jwt服务 /// </summary> public void AddJwtService() { } /// <summary> /// 返回 /// </summary> /// <returns></returns> public string GetJwtToken() { //简单测试一下 var token = JsonConvert.SerializeObject(JwtConfig); return token; } }
我们应该使用Ioc的方式注入JwtHelper
var jwtConfig = new JwtConfig();
builder.Configuration.Bind("JwtConfig",jwtConfig);
var jwtHelper = new JwtHelper() {
JwtConfig = jwtConfig
};
//将JwtHelper添加到Services里面
builder.Services.AddSingleton<JwtHelper>(jwtHelper);
然后在控制器里面获取Token
/// <summary> /// 测试控制器 /// </summary> [Route("api/[controller]/[action]")] [ApiController] public class TestController : ControllerBase { private JwtHelper jwtHelper; /// <summary> /// 通过Ioc得到Jwt /// </summary> /// <param name="jwtHelper"></param> public TestController(JwtHelper jwtHelper) { this.jwtHelper = jwtHelper; } /// <summary> /// 测试返回值 /// </summary> /// <returns></returns> [HttpGet] public WebMsg GetTest() { return new WebMsg("测试返回值"); } /// <summary> /// 获取JwtToken /// </summary> /// <returns></returns> [HttpGet] public WebMsg GetJwtToken() { var token = jwtHelper.GetJwtToken(); return new WebMsg(token); } }
接下来我们修改一下GetJwtToken这个函数即可
using Microsoft.IdentityModel.Tokens; using System.Text; namespace JtwTestWebApi.Models { public class JwtConfig { /// <summary> /// 密钥 /// </summary> public string SecretKey { get; set; } /// <summary> /// 发布者 /// </summary> public string Issuer { get; set; } /// <summary> /// 接受者 /// </summary> public string Audience { get; set; } /// <summary> /// 过期时间(min) /// </summary> public int Expired { get; set; } /// <summary> /// 生效时间 /// </summary> public DateTime NotBefore => DateTime.Now; /// <summary> /// 过期时间 /// </summary> public DateTime Expiration => DateTime.Now.AddMinutes(Expired); /// <summary> /// 密钥Bytes /// </summary> private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)); /// <summary> /// 加密后的密钥,使用HmacSha256加密 /// </summary> public SigningCredentials SigningCredentials => new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256); } }
在JwtHelper里面使用
/// <summary> /// 最简单的JwtToken /// </summary> /// <returns></returns> public string GetJwtToken() { var claims = new List<Claim>(); var jwtSecurityToken = new JwtSecurityToken( JwtConfig.Issuer, JwtConfig.Audience, claims, JwtConfig.NotBefore, JwtConfig.Expiration, JwtConfig.SigningCredentials ); var token = new JwtSecurityTokenHandler().WriteToken( jwtSecurityToken ); return token; }
运行报错,说明密钥的长度太短了,我们加长一下
返回成功!
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MTAzODUyODgsImV4cCI6MTcxMDM4NzA4OCwiaXNzIjoiWGlhb1dhbmciLCJhdWQiOiJYaWFvV2FuZyJ9.v7UbQOba7VoNgoiRsoIQkFJKQTFBMLJlYEKBIfdFV4o
加密了,肯定要有对应的授权
在JwtConfig里面添加对应的授权
public class JwtConfig { /// <summary> /// 密钥 /// </summary> public string SecretKey { get; set; } /// <summary> /// 发布者 /// </summary> public string Issuer { get; set; } /// <summary> /// 接受者 /// </summary> public string Audience { get; set; } /// <summary> /// 过期时间(min) /// </summary> public int Expired { get; set; } /// <summary> /// 生效时间 /// </summary> public DateTime NotBefore => DateTime.Now; /// <summary> /// 过期时间 /// </summary> public DateTime Expiration => DateTime.Now.AddMinutes(Expired); /// <summary> /// 密钥Bytes /// </summary> private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)); /// <summary> /// 加密后的密钥,使用HmacSha256加密 /// </summary> public SigningCredentials SigningCredentials => new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256); /// <summary> /// 认证用的密钥 /// </summary> public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)); }
在JwtHelper中
/// <summary> /// 添加Jwt服务 /// </summary> public void AddJwtService(IServiceCollection services) { services.AddAuthentication(option => { //认证middleware配置 option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { //Token颁发机构 ValidIssuer = JwtConfig.Issuer, //颁发给谁 ValidAudience = JwtConfig.Audience, //这里的key要进行加密 IssuerSigningKey = JwtConfig.SymmetricSecurityKey, //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比 ValidateLifetime = true, }; }); }
调用JwtHelper
var jwtConfig = new JwtConfig();
builder.Configuration.Bind("JwtConfig",jwtConfig);
var jwtHelper = new JwtHelper() {
JwtConfig = jwtConfig
};
//将JwtHelper添加到Services里面
builder.Services.AddSingleton<JwtHelper>(jwtHelper);
jwtHelper.AddJwtService(builder.Services);
在app中启用
app.UseHttpsRedirection();
app.UseAuthentication();//要在授权之前认证,这个和[Authorize]特性有关
app.UseAuthorization();
一定要在za之前使用ca
我们接口最好单独开一个获取Token的接口
TestController
using JtwTestWebApi.Models; using JtwTestWebApi.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace JtwTestWebApi.Controllers { /// <summary> /// 测试控制器 /// </summary> [Route("api/[controller]/[action]")] [ApiController] public class TestController : ControllerBase { private JwtHelper jwtHelper; /// <summary> /// 通过Ioc得到Jwt /// </summary> /// <param name="jwtHelper"></param> public TestController(JwtHelper jwtHelper) { this.jwtHelper = jwtHelper; } /// <summary> /// 测试返回值 /// </summary> /// <returns></returns> [HttpGet] public WebMsg GetTest() { return new WebMsg("测试返回值"); } /// <summary> /// 获取JwtToken /// </summary> /// <returns></returns> [HttpGet] public WebMsg GetJwtToken() { var token = jwtHelper.GetJwtToken(); return new WebMsg(token); } /// <summary> /// 可以在方法前面加Authorize /// </summary> /// <returns></returns> [Authorize] [HttpGet] public WebMsg GetByJwt() { return new WebMsg("Jwt测试成功!"); } } }
JtwTestWebApi
如果我们在类前面添加了[Authorize],下面全部的接口都有Jwt认证。想放开Jtw认证,使用[AllowAnonymous]标记方法即可
using JtwTestWebApi.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace JtwTestWebApi.Controllers { /// <summary> /// Jwt测试类 /// </summary> [Route("api/[controller]/[action]")] [ApiController] [Authorize] public class JwtTestController : ControllerBase { /// <summary> /// 不需要Jwt /// </summary> /// <returns></returns> [AllowAnonymous] [HttpGet] public WebMsg NoJwtGet() { return new WebMsg("我不需要Jwt"); } /// <summary> /// 需要Jwt /// </summary> /// <returns></returns> [HttpGet] public WebMsg JwtGet() { return new WebMsg("我需要Jwt"); } } }
认证失败的结果
既然我们拦截了请求,我们也得对符合规范的请求放行。那什么样的请求能放行呢?在Header中的【Authorization】中添加【Bearer {token}】才能放行。
测试成功!
Jwt的过期是由两个数据综合相加的。一个是生成Token的过期时间
一个是缓冲时间,默认5分钟。因为服务器的时间可能不同步。
所以总过期时间=过期时间+缓冲时间
为了更简单的获取Token,我们直接把返回的Token自带[Bearer ]这个前缀好了
在JwtHelper中添加全局静态方法
/// <summary> /// Swagger添加Jwt功能 /// </summary> /// <param name="options"></param> public static void SwaggerAddJwtHeader(SwaggerGenOptions options) { options.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer", } }, new string[] { } } }); options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格", Name = "Authorization",//jwt默认的参数名称 In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中) Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT", Scheme = "Bearer" }); }
因为我们的Jwt是自带【Bearer 】这个请求头的,所以去掉前面的头,里面其实是可以解密的
/// <summary> /// 获取Jwt的信息 /// </summary> /// <param name="request"></param> /// <returns></returns> public IEnumerable<Claim> Decode(HttpRequest request) { var authorization = request.Headers["Authorization"].ToString(); //因为我们的Jwt是自带【Bearer 】这个请求头的,所以去掉前面的头 var auth = authorization.Split(" ")[1]; var handler = new JwtSecurityTokenHandler(); //反解密,获取其中的Claims var payload = handler.ReadJwtToken(auth).Payload; var claims = payload.Claims; return claims; }
解密能拿到是因为我们加密的时候就已经放进去了
/// <summary> /// 添加claims信息 /// </summary> /// <param name="claims"></param> /// <returns></returns> public string GetJwtToken(List<Claim> claims) { var jwtSecurityToken = new JwtSecurityToken( JwtConfig.Issuer, JwtConfig.Audience, claims, JwtConfig.NotBefore, JwtConfig.Expiration, JwtConfig.SigningCredentials ); var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); token = "Bearer " + token; return token; }
/// <summary>
/// 获取JwtToken
/// </summary>
/// <returns></returns>
[HttpGet]
public WebMsg GetJwtToken2()
{
//我们加密的时候,就放进去了一些额外的信息
var token = jwtHelper.GetJwtToken(new List<Claim>()
{
new Claim("UserId","2"),
new Claim("UserName","3")
});
return new WebMsg(token);
}
/// <summary>
/// 可以在方法前面加Authorize
/// </summary>
/// <returns></returns>
[Authorize]
[HttpGet]
public WebMsg GetByJwt()
{
//获取解密后的Claim
var dic = jwtHelper.Decode(this.Request);
return new WebMsg("Jwt测试成功!");
}
这样我们就可以把一些比较隐私的数据放在里面,比如用户Id,这样防止泄漏。
using JtwTestWebApi.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Swashbuckle.AspNetCore.SwaggerGen; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace JtwTestWebApi.Utils { public class JwtHelper { public JwtConfig JwtConfig { get; set; } public JwtHelper() { } /// <summary> /// 添加Jwt服务 /// </summary> public void AddJwtService(IServiceCollection services) { services.AddAuthentication(option => { //认证middleware配置 option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { //Token颁发机构 ValidIssuer = JwtConfig.Issuer, //颁发给谁 ValidAudience = JwtConfig.Audience, //这里的key要进行加密 IssuerSigningKey = JwtConfig.SymmetricSecurityKey, //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比 ValidateLifetime = true, ValidateIssuer = true, ValidateAudience = true, ValidateIssuerSigningKey = true, RequireExpirationTime = true, }; }); } /// <summary> /// 最简单的JwtToken /// </summary> /// <returns></returns> public string GetJwtToken() { var claims = new List<Claim>(); var jwtSecurityToken = new JwtSecurityToken( JwtConfig.Issuer, JwtConfig.Audience, claims, JwtConfig.NotBefore, JwtConfig.Expiration, JwtConfig.SigningCredentials ); var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); token = "Bearer " + token; return token; } /// <summary> /// 添加claims信息 /// </summary> /// <param name="claims"></param> /// <returns></returns> public string GetJwtToken(List<Claim> claims) { var jwtSecurityToken = new JwtSecurityToken( JwtConfig.Issuer, JwtConfig.Audience, claims, JwtConfig.NotBefore, JwtConfig.Expiration, JwtConfig.SigningCredentials ); var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); token = "Bearer " + token; return token; } /// <summary> /// UserModel类Token /// </summary> /// <param name="user"></param> /// <returns></returns> public string GetJwtToken(JwtUserModel user) { var claims = new List<Claim>() { new Claim("UserId",user.UserId.ToString()), new Claim("UserName",user.UserName), new Claim("UserType",user.UserType.ToString()), }; return GetJwtToken(claims); } /// <summary> /// Swagger添加Jwt功能 /// </summary> /// <param name="options"></param> public static void SwaggerAddJwtHeader(SwaggerGenOptions options) { options.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer", } }, new string[] { } } }); options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格", Name = "Authorization",//jwt默认的参数名称 In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中) Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT", Scheme = "Bearer" }); } /// <summary> /// 获取Jwt的信息 /// </summary> /// <param name="request"></param> /// <returns></returns> public IEnumerable<Claim> Decode(HttpRequest request) { var authorization = request.Headers["Authorization"].ToString(); //因为我们的Jwt是自带【Bearer 】这个请求头的,所以去掉前面的头 var auth = authorization.Split(" ")[1]; var handler = new JwtSecurityTokenHandler(); //反解密,获取其中的Claims var payload = handler.ReadJwtToken(auth).Payload; var claims = payload.Claims; return claims; } /// <summary> /// 解析得到User /// </summary> /// <param name="request"></param> /// <returns></returns> public JwtUserModel DecodeToUser(HttpRequest request) { var claims = Decode(request); var user = new JwtUserModel() { UserId = claims.Where(t => t.Type == "UserId").First().Value, UserName = claims.Where(t => t.Type == "UserName").First().Value, UserType = claims.Where(t => t.Type == "UserType").First().Value }; return user; } } }
[HttpGet] public WebMsg GetJwtToken3() { var token = jwtHelper.GetJwtToken(new JwtUserModel() { UserName = "小王", UserId = "32", UserType = "admin" }); return new WebMsg(token); } [Authorize] [HttpGet] public WebMsg GetByJwt3() { var dic = jwtHelper.DecodeToUser(this.Request); return new WebMsg("Jwt测试成功!"); }
运行得到结果
介绍三种授权方式(Policy、Role、Scheme),Scheme这种用的太少了,我们就简单讲解一下Policy和Role这两种方式。Policy和Role的核心就在于我们封装Token的时候添加的Claim对象。大家看到这里就会发现,其实Jwt的核心就是装包和拆包。网页请求Token的时候拿到了个包裹,网页发送Http的时候解开这个包裹。
我写了一会,感觉有点累了。这里有个别人写好的代码,有兴趣的自己去看吧。
获取不同权限的Token
/// <summary> /// 获取Role = admin的Token /// </summary> /// <returns></returns> [HttpGet] public WebMsg GetJwtToken_Admin() { var token = jwtHelper.GetJwtToken(new List<Claim>() { new Claim(ClaimTypes.Role,"admin") }); return new WebMsg(token); } /// <summary> /// 获取Role = user /// </summary> /// <returns></returns> [HttpGet] public WebMsg GetJwtToken_User() { var token = jwtHelper.GetJwtToken(new List<Claim>() { new Claim(ClaimTypes.Role,"user") }); return new WebMsg(token); } /// <summary> /// 获取Role = admin和user /// </summary> /// <returns></returns> [HttpGet] public WebMsg GetJwtToken_UserAndAdmin() { var token = jwtHelper.GetJwtToken(new List<Claim>() { new Claim(ClaimTypes.Role,"user"), new Claim(ClaimTypes.Role,"admin") }); return new WebMsg(token); }
/// <summary> /// 需要role=admin /// </summary> /// <returns></returns> [HttpGet] [Authorize(Roles = "admin")] public WebMsg AdminGet() { return new WebMsg("admin"); } /// <summary> /// role=user /// </summary> /// <returns></returns> [HttpGet] [Authorize(Roles = "user")] public WebMsg UserGet() { return new WebMsg("user"); } /// <summary> /// role = admin或user /// </summary> /// <returns></returns> [HttpGet] [Authorize(Roles = "admin,user")] public WebMsg AdminOrUserGet() { return new WebMsg("admin or user"); } /// <summary> /// role=admin和user /// </summary> /// <returns></returns> [HttpGet] [Authorize(Roles = "admin")] [Authorize(Roles = "user")] public WebMsg AdminAndUserGet() { return new WebMsg("admin and user"); }
这里推荐使用enum枚举类型,这样不会出现拼写错误
using Microsoft.IdentityModel.Tokens; using System.Text; namespace JtwTestWebApi.Models { public class JwtConfig { /// <summary> /// 密钥 /// </summary> public string SecretKey { get; set; } /// <summary> /// 发布者 /// </summary> public string Issuer { get; set; } /// <summary> /// 接受者 /// </summary> public string Audience { get; set; } /// <summary> /// 过期时间(min) /// </summary> public int Expired { get; set; } /// <summary> /// 生效时间 /// </summary> public DateTime NotBefore => DateTime.Now; /// <summary> /// 过期时间 /// </summary> public DateTime Expiration => DateTime.Now.AddMinutes(Expired); /// <summary> /// 密钥Bytes /// </summary> private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)); /// <summary> /// 加密后的密钥,使用HmacSha256加密 /// </summary> public SigningCredentials SigningCredentials => new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256); /// <summary> /// 认证用的密钥 /// </summary> public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)); } }
using JtwTestWebApi.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Swashbuckle.AspNetCore.SwaggerGen; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace JtwTestWebApi.Utils { public class JwtHelper { public JwtConfig JwtConfig { get; set; } public JwtHelper() { } /// <summary> /// 添加Jwt服务 /// </summary> public void AddJwtService(IServiceCollection services) { services.AddAuthentication(option => { //认证middleware配置 option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { //Token颁发机构 ValidIssuer = JwtConfig.Issuer, //颁发给谁 ValidAudience = JwtConfig.Audience, //这里的key要进行加密 IssuerSigningKey = JwtConfig.SymmetricSecurityKey, //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比 ValidateLifetime = true, ValidateIssuer = true, ValidateAudience = true, ValidateIssuerSigningKey = true, RequireExpirationTime = true, }; }); } /// <summary> /// 最简单的JwtToken /// </summary> /// <returns></returns> public string GetJwtToken() { var claims = new List<Claim>(); var jwtSecurityToken = new JwtSecurityToken( JwtConfig.Issuer, JwtConfig.Audience, claims, JwtConfig.NotBefore, JwtConfig.Expiration, JwtConfig.SigningCredentials ); var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); token = "Bearer " + token; return token; } /// <summary> /// 添加claims信息 /// </summary> /// <param name="claims"></param> /// <returns></returns> public string GetJwtToken(List<Claim> claims) { var jwtSecurityToken = new JwtSecurityToken( JwtConfig.Issuer, JwtConfig.Audience, claims, JwtConfig.NotBefore, JwtConfig.Expiration, JwtConfig.SigningCredentials ); var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); token = "Bearer " + token; return token; } /// <summary> /// UserModel类Token /// </summary> /// <param name="user"></param> /// <returns></returns> public string GetJwtToken(JwtUserModel user) { var claims = new List<Claim>() { new Claim("UserId",user.UserId.ToString()), new Claim("UserName",user.UserName), new Claim("UserType",user.UserType.ToString()), }; return GetJwtToken(claims); } /// <summary> /// Swagger添加Jwt功能 /// </summary> /// <param name="options"></param> public static void SwaggerAddJwtHeader(SwaggerGenOptions options) { options.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer", } }, new string[] { } } }); options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格", Name = "Authorization",//jwt默认的参数名称 In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中) Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT", Scheme = "Bearer" }); } /// <summary> /// 获取Jwt的信息 /// </summary> /// <param name="request"></param> /// <returns></returns> public IEnumerable<Claim> Decode(HttpRequest request) { var authorization = request.Headers["Authorization"].ToString(); //因为我们的Jwt是自带【Bearer 】这个请求头的,所以去掉前面的头 var auth = authorization.Split(" ")[1]; var handler = new JwtSecurityTokenHandler(); //反解密,获取其中的Claims var payload = handler.ReadJwtToken(auth).Payload; var claims = payload.Claims; return claims; } /// <summary> /// 解析得到User /// </summary> /// <param name="request"></param> /// <returns></returns> public JwtUserModel DecodeToUser(HttpRequest request) { var claims = Decode(request); var user = new JwtUserModel() { UserId = claims.Where(t => t.Type == "UserId").First().Value, UserName = claims.Where(t => t.Type == "UserName").First().Value, UserType = claims.Where(t => t.Type == "UserType").First().Value }; return user; } } }
namespace JtwTestWebApi.Models
{
public class JwtUserModel
{
public string UserId { get; set; }
public string UserName { get; set; }
public string UserType { get; set; }
}
}
using JtwTestWebApi.Models; using JtwTestWebApi.Utils; using Microsoft.Extensions.Configuration; using Microsoft.OpenApi.Models; using System.IdentityModel.Tokens.Jwt; using System.Reflection; namespace JtwTestWebApi { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var MyPolicy = "MyPolicy"; // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //为了防止配套太多,代码混乱。这里自定义了一个方法 AddMyService(builder); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { } app.UseSwagger(); app.UseSwaggerUI(); app.UseStatusCodePagesWithRedirects("/swagger/index.html"); app.UseHttpsRedirection(); app.UseAuthentication();//要在授权之前认证,这个和[Authorize]特性有关 app.UseAuthorization(); app.MapControllers(); app.Run(); } /// <summary> /// 为了防止代码过于臃肿,将新的配置放在这里写 /// </summary> /// <param name="builder"></param> public static void AddMyService(WebApplicationBuilder builder) { #region 默认的Webapi配置 builder.Services.AddCors(options => { options.AddPolicy("MyPolicy", policy => { policy.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod(); }); }); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", Title = "API标题", Description = $"API描述,v1版本" }); var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; //IncludeXmlComments 第二参数 true 则显示 控制器 注释 options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename), true); JwtHelper.SwaggerAddJwtHeader(options); }); #endregion #region 添加Jwt服务 var jwtConfig = new JwtConfig(); builder.Configuration.Bind("JwtConfig",jwtConfig); var jwtHelper = new JwtHelper() { JwtConfig = jwtConfig }; //将JwtHelper添加到Services里面 builder.Services.AddSingleton<JwtHelper>(jwtHelper); jwtHelper.AddJwtService(builder.Services); #endregion } } }
appsettings.json中添加
"JwtConfig": {
"SecretKey": "lisheng741@qq.comlisheng741@qq.com",
"Issuer": "WebAppIssuer",
"Audience": "WebAppAudience",
"Expired": 30 // 过期时间(30min)
}
Jwt其实也不是特别难,就是第一次配置的时候容易被绕晕。Jwt的策略我暂时先跳过了,对于解决普通问题一般来说已经够用了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。