![cover](https://img-blog.csdnimg.cn/direct/8ab301251e064e7c8c43bd56096fccaf.png)
ASP.NET Core 8.0 WebApi 从零开始学习JWT登录认证
我一起写后端Api我都是直接裸连的,但是现在为了规范一些,我还是打算上一个JWT功能Jwt其实也不是特别难,就是第一次配置的时候容易被绕晕。Jwt的策略我暂时先跳过了,对于解决普通问题一般来说已经够用了。
前言
我一起写后端Api我都是直接裸连的,但是现在为了规范一些,我还是打算上一个JWT功能
相关链接
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_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的加密解密算法是可以自己搭配的,选择加密算法,这里我就不展开了。详细可以看官方文档
在WebApi中简单使用
首先我们之前用到了可逆的加密和解密。那我们就需要用一个判断是否过期的类
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": "操作成功!"
}
WebApi 授权,博客太老了,尝试失败
这里我们就用简单的授权,复杂的授权可以自己去看微软官方文档
然后我发现.net core 8.0的认证方式好像变了,我按照博客的写法发现不让我重写了
这个方法是.net framework上面用的,.net core 用不了。我想还是算了,用.net core 的方法好了
WebApi .net core 8.0 最新版Jwt (微软官方集成)
我看了一天的博客,终于解决了JWT认证的问题。
重新新建一个Webapi
这里我就不多说了
我们还是要改一下的,因为之前的我测试的时候,发现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);
});
}
}
}
简单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测试
测试成功!
过期测试
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();
//反解密,获取其中的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测试成功!");
}
运行得到结果
Jwt授权模式基础讲解
介绍三种授权方式(Policy、Role、Scheme),Scheme这种用的太少了,我们就简单讲解一下Policy和Role这两种方式。Policy和Role的核心就在于我们封装Token的时候添加的Claim对象。大家看到这里就会发现,其实Jwt的核心就是装包和拆包。网页请求Token的时候拿到了个包裹,网页发送Http的时候解开这个包裹。
我写了一会,感觉有点累了。这里有个别人写好的代码,有兴趣的自己去看吧。
简单的【角色授权】
获取不同权限的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();
//反解密,获取其中的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的策略我暂时先跳过了,对于解决普通问题一般来说已经够用了。
![Logo](https://devpress.csdnimg.cn/79de2bf0b7994defa4242ef90d5513fa.jpg)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)