文章目录
- 前言
- 相关链接
- Nuget选择
- 知识补充
- JWT不是加密算法
- 可逆加密和不可逆加密
- 普通Jwt(不推荐)
- 项目环境
- Nuget
- 最小JWT测试
- 在WebApi中简单使用
- 简单使用
- 运行结果
- WebApi 授权,博客太老了,尝试失败
- WebApi .net core 8.0 最新版Jwt (微软官方集成)
- 重新新建一个Webapi
- 简单Controller
- 最简单的Jwt认证
- 获取JwtConfig
- 新建JwtHelper类
- 完善JwtConfig
- 授权
- 授权测试
- 能通过Jwt的请求
- PostMan测试
- 过期测试
- Swagger 全局Header
- 获取Jwt中的信息
- 简单封装一下
- Jwt授权模式基础讲解
- 简单的【角色授权】
- 获取不同权限的Token
- 不同权限的jwt认证
- 封装好的代码
- 总结
前言
我一起写后端Api我都是直接裸连的,但是现在为了规范一些,我还是打算上一个JWT功能
相关链接
ASP.NET Web API 2系列(四):基于JWT的token身份认证方案
Jwt-dotnet github
Nuget选择
选好了模板,就进去看看号了,42M的下载量已经很高了,一般来说,只要超过500k的下载量,基本就是一个稳定的代码仓库了。
进去看看里面写的怎么样
可以看到写的还是比较清晰的
知识补充
JWT不是加密算法
JWT全名 JSON Web Token。Token这部分才是加密得出来的,JWT只是一个网页认证策略。
可逆加密和不可逆加密
无论是可逆加密还是不可逆加密,都需要一个自定义的【Key】来辅助加密。可逆加密就类似于一元二次方程,key就类似于修改方程各项的系数,【Key=125】得到【x^2+2x+5】。我的原文是【1】,得到的密文就是计算后的结果【8】。所以加密比解密要方便。
不可逆加密就是不能逆向解决的方程,比如高阶方程【x^7- x^3 +5x-3】,正向好算,逆向基本不可解。
所以现实使用的时候,可逆加密一般是解密后使用。因为可逆,所以可以用于复杂的敏感信息。
不可逆加密是判断加密后的的密文是否相同,比如密码,是不能泄漏的,所以我们的密码只能重置,因为系统也不知道原密码是什么。
普通Jwt(不推荐)
项目环境
- visual studio 2022
- .net core 8.0
- win 10
Nuget
最小JWT测试
我们先不管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_tlDhHpkjAkJpRRdnqz6Jn2FmP0qONAI4oNl8Ye9wMvar 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();//序列化JsonIBase64UrlEncoder 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的加密解密算法是可以自己搭配的,选择加密算法,这里我就不展开了。详细可以看官方文档
在WebApi中简单使用
首先我们之前用到了可逆的加密和解密。那我们就需要用一个判断是否过期的类
public class UserJwtLogin
{/// <summary>/// 用户Id,因为数据库的Id是唯一的,不会重复/// </summary>public long UserId { get; set; } /// <summary>/// 过期时间/// </summary>public DateTime ExpireTime { get; set; }
}
详细的解决方案就在这个文章里面了,我就不照抄了,拾人牙慧。
ASP.NET Web API 2系列(四):基于JWT的token身份认证方案
我这里是这么写的
新建一个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();//序列化JsonIBase64UrlEncoder 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表的Idvar 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": "操作成功!"
}
WebApi 授权,博客太老了,尝试失败
这里我们就用简单的授权,复杂的授权可以自己去看微软官方文档
ASP.NET Core 中的简单授权
然后我发现.net core 8.0的认证方式好像变了,我按照博客的写法发现不让我重写了
这个方法是.net framework上面用的,.net core 用不了。我想还是算了,用.net core 的方法好了
WebApi .net core 8.0 最新版Jwt (微软官方集成)
我看了一天的博客,终于解决了JWT认证的问题。
在没有 ASP.NET CoreIdentity的情况下使用cookie身份验证
ASP.NET Core 6.0 添加 JWT 认证和授权
.Net Core WebApi集成JWT实现身份认证
重新新建一个Webapi
这里我就不多说了
.NET Core webapi 从零开始在IIS上面发布后端接口
我们还是要改一下的,因为之前的我测试的时候,发现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/swashbucklebuilder.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);});}}
}
简单Controller
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认证
我们知道Jwt其实就是生成和验证两个功能。微软把这个功能集成到一个JwtBearer库里面了。
为了方便Json打印,添加个Newtonsoft
获取JwtConfig
我们在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);
新建JwtHelper类
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这个函数即可
完善JwtConfig
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");}}
}
认证失败的结果
能通过Jwt的请求
既然我们拦截了请求,我们也得对符合规范的请求放行。那什么样的请求能放行呢?在Header中的【Authorization】中添加【Bearer {token}】才能放行。
PostMan测试
测试成功!
过期测试
.NetCore JWT token过期时间设置
Jwt的过期是由两个数据综合相加的。一个是生成Token的过期时间
一个是缓冲时间,默认5分钟。因为服务器的时间可能不同步。
所以总过期时间=过期时间+缓冲时间
为了更简单的获取Token,我们直接把返回的Token自带[Bearer ]这个前缀好了
Swagger 全局Header
在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中的信息
因为我们的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();//反解密,获取其中的Claimsvar 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()
{//获取解密后的Claimvar 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();//反解密,获取其中的Claimsvar 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测试成功!");
}
运行得到结果
Jwt授权模式基础讲解
介绍三种授权方式(Policy、Role、Scheme),Scheme这种用的太少了,我们就简单讲解一下Policy和Role这两种方式。Policy和Role的核心就在于我们封装Token的时候添加的Claim对象。大家看到这里就会发现,其实Jwt的核心就是装包和拆包。网页请求Token的时候拿到了个包裹,网页发送Http的时候解开这个包裹。
我写了一会,感觉有点累了。这里有个别人写好的代码,有兴趣的自己去看吧。
ASP.NET Core 6.0 添加 JWT 认证和授权
简单的【角色授权】
获取不同权限的Token
获取不同权限的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);
}
不同权限的jwt认证
/// <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();//反解密,获取其中的Claimsvar 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/swashbucklebuilder.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的策略我暂时先跳过了,对于解决普通问题一般来说已经够用了。