前言
我一起寫后端Api我都是直接裸連的,但是現(xiàn)在為了規(guī)范一些,我還是打算上一個JWT功能
相關(guān)鏈接
ASP.NET Web API 2系列(四):基于JWT的token身份認(rèn)證方案
Jwt-dotnet github
Nuget選擇
選好了模板,就進(jìn)去看看號了,42M的下載量已經(jīng)很高了,一般來說,只要超過500k的下載量,基本就是一個穩(wěn)定的代碼倉庫了。
進(jìn)去看看里面寫的怎么樣
可以看到寫的還是比較清晰的
知識補充
JWT不是加密算法
JWT全名 JSON Web Token。Token這部分才是加密得出來的,JWT只是一個網(wǎng)頁認(rèn)證策略。
可逆加密和不可逆加密
無論是可逆加密還是不可逆加密,都需要一個自定義的【Key】來輔助加密??赡婕用芫皖愃朴谝辉畏匠?,key就類似于修改方程各項的系數(shù),【Key=125】得到【x^2+2x+5】。我的原文是【1】,得到的密文就是計算后的結(jié)果【8】。所以加密比解密要方便。
不可逆加密就是不能逆向解決的方程,比如高階方程【x^7- x^3 +5x-3】,正向好算,逆向基本不可解。
所以現(xiàn)實使用的時候,可逆加密一般是解密后使用。因為可逆,所以可以用于復(fù)雜的敏感信息。
不可逆加密是判斷加密后的的密文是否相同,比如密碼,是不能泄漏的,所以我們的密碼只能重置,因為系統(tǒng)也不知道原密碼是什么。
普通Jwt(不推薦)
項目環(huán)境
- 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的加密解密算法是可以自己搭配的,選擇加密算法,這里我就不展開了。詳細(xì)可以看官方文檔
在WebApi中簡單使用
首先我們之前用到了可逆的加密和解密。那我們就需要用一個判斷是否過期的類
public class UserJwtLogin
{
/// <summary>
/// 用戶Id,因為數(shù)據(jù)庫的Id是唯一的,不會重復(fù)
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 過期時間
/// </summary>
public DateTime ExpireTime { get; set; }
}
詳細(xì)的解決方案就在這個文章里面了,我就不照抄了,拾人牙慧。
ASP.NET Web API 2系列(四):基于JWT的token身份認(rèn)證方案
我這里是這么寫的
新建一個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,因為數(shù)據(jù)庫的Id是唯一的,不會重復(fù)
/// </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")
{
//這里是模擬拿到數(shù)據(jù)庫的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,
};
}
}
}
運行結(jié)果
{
"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 授權(quán),博客太老了,嘗試失敗
這里我們就用簡單的授權(quán),復(fù)雜的授權(quán)可以自己去看微軟官方文檔
ASP.NET Core 中的簡單授權(quán)
然后我發(fā)現(xiàn).net core 8.0的認(rèn)證方式好像變了,我按照博客的寫法發(fā)現(xiàn)不讓我重寫了
這個方法是.net framework上面用的,.net core 用不了。我想還是算了,用.net core 的方法好了
WebApi .net core 8.0 最新版Jwt (微軟官方集成)
我看了一天的博客,終于解決了JWT認(rèn)證的問題。
在沒有 ASP.NET CoreIdentity的情況下使用cookie身份驗證
ASP.NET Core 6.0 添加 JWT 認(rèn)證和授權(quán)
.Net Core WebApi集成JWT實現(xiàn)身份認(rèn)證
重新新建一個Webapi
這里我就不多說了
.NET Core webapi 從零開始在IIS上面發(fā)布后端接口
我們還是要改一下的,因為之前的我測試的時候,發(fā)現(xiàn)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標(biāo)題",
Description = $"API描述,v1版本"
});
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
//IncludeXmlComments 第二參數(shù) 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認(rèn)證
我們知道Jwt其實就是生成和驗證兩個功能。微軟把這個功能集成到一個JwtBearer庫里面了。
為了方便Json打印,添加個Newtonsoft
獲取JwtConfig
我們在appsetting.json里面添加
"JwtConfig": {
"SecretKey": "123123123123", // 密鑰 可以是guid 也可以是隨便一個字符串
"Issuer": "XiaoWang", // 頒發(fā)者
"Audience": "XiaoWang", // 接收者
"Expired": 30 // 過期時間(30min)
}
public class JwtConfig
{
/// <summary>
/// 密鑰
/// </summary>
public string SecretKey { get; set; }
/// <summary>
/// 發(fā)布者
/// </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服務(wù)
/// </summary>
public void AddJwtService()
{
}
/// <summary>
/// 返回
/// </summary>
/// <returns></returns>
public string GetJwtToken()
{
//簡單測試一下
var token = JsonConvert.SerializeObject(JwtConfig);
return token;
}
}
我們應(yīng)該使用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這個函數(shù)即可
完善JwtConfig
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace JtwTestWebApi.Models
{
public class JwtConfig
{
/// <summary>
/// 密鑰
/// </summary>
public string SecretKey { get; set; }
/// <summary>
/// 發(fā)布者
/// </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
授權(quán)
加密了,肯定要有對應(yīng)的授權(quán)
在JwtConfig里面添加對應(yīng)的授權(quán)
public class JwtConfig
{
/// <summary>
/// 密鑰
/// </summary>
public string SecretKey { get; set; }
/// <summary>
/// 發(fā)布者
/// </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>
/// 認(rèn)證用的密鑰
/// </summary>
public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
}
在JwtHelper中
/// <summary>
/// 添加Jwt服務(wù)
/// </summary>
public void AddJwtService(IServiceCollection services)
{
services.AddAuthentication(option =>
{
//認(rèn)證middleware配置
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
//Token頒發(fā)機(jī)構(gòu)
ValidIssuer = JwtConfig.Issuer,
//頒發(fā)給誰
ValidAudience = JwtConfig.Audience,
//這里的key要進(jìn)行加密
IssuerSigningKey = JwtConfig.SymmetricSecurityKey,
//是否驗證Token有效期,使用當(dāng)前時間與Token的Claims中的NotBefore和Expires對比
ValidateLifetime = true,
};
});
}
調(diào)用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();//要在授權(quán)之前認(rèn)證,這個和[Authorize]特性有關(guān)
app.UseAuthorization();
一定要在za之前使用ca
授權(quán)測試
我們接口最好單獨開一個獲取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認(rèn)證。想放開Jtw認(rèn)證,使用[AllowAnonymous]標(biāo)記方法即可
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");
}
}
}
認(rèn)證失敗的結(jié)果
能通過Jwt的請求
既然我們攔截了請求,我們也得對符合規(guī)范的請求放行。那什么樣的請求能放行呢?在Header中的【Authorization】中添加【Bearer {token}】才能放行。
PostMan測試
測試成功!
過期測試
.NetCore JWT token過期時間設(shè)置
Jwt的過期是由兩個數(shù)據(jù)綜合相加的。一個是生成Token的過期時間
一個是緩沖時間,默認(rèn)5分鐘。因為服務(wù)器的時間可能不同步。
所以總過期時間=過期時間+緩沖時間
為了更簡單的獲取Token,我們直接把返回的Token自帶[Bearer ]這個前綴好了
Swagger 全局Header
在JwtHelper中添加全局靜態(tài)方法
/// <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授權(quán)(數(shù)據(jù)將在請求頭中進(jìn)行傳輸) 在下方輸入Bearer {token} 即可,注意兩者之間有空格",
Name = "Authorization",//jwt默認(rèn)的參數(shù)名稱
In = ParameterLocation.Header,//jwt默認(rèn)存放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;
}
解密能拿到是因為我們加密的時候就已經(jīng)放進(jìn)去了
/// <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()
{
//我們加密的時候,就放進(jìn)去了一些額外的信息
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測試成功!");
}
這樣我們就可以把一些比較隱私的數(shù)據(jù)放在里面,比如用戶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服務(wù)
/// </summary>
public void AddJwtService(IServiceCollection services)
{
services.AddAuthentication(option =>
{
//認(rèn)證middleware配置
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
//Token頒發(fā)機(jī)構(gòu)
ValidIssuer = JwtConfig.Issuer,
//頒發(fā)給誰
ValidAudience = JwtConfig.Audience,
//這里的key要進(jìn)行加密
IssuerSigningKey = JwtConfig.SymmetricSecurityKey,
//是否驗證Token有效期,使用當(dāng)前時間與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授權(quán)(數(shù)據(jù)將在請求頭中進(jìn)行傳輸) 在下方輸入Bearer {token} 即可,注意兩者之間有空格",
Name = "Authorization",//jwt默認(rèn)的參數(shù)名稱
In = ParameterLocation.Header,//jwt默認(rèn)存放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測試成功!");
}
運行得到結(jié)果
Jwt授權(quán)模式基礎(chǔ)講解
介紹三種授權(quán)方式(Policy、Role、Scheme),Scheme這種用的太少了,我們就簡單講解一下Policy和Role這兩種方式。Policy和Role的核心就在于我們封裝Token的時候添加的Claim對象。大家看到這里就會發(fā)現(xiàn),其實Jwt的核心就是裝包和拆包。網(wǎng)頁請求Token的時候拿到了個包裹,網(wǎng)頁發(fā)送Http的時候解開這個包裹。
我寫了一會,感覺有點累了。這里有個別人寫好的代碼,有興趣的自己去看吧。
ASP.NET Core 6.0 添加 JWT 認(rèn)證和授權(quán)
簡單的【角色授權(quán)】
獲取不同權(quán)限的Token
獲取不同權(quán)限的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);
}
不同權(quán)限的jwt認(rèn)證
/// <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枚舉類型,這樣不會出現(xiàn)拼寫錯誤
封裝好的代碼
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace JtwTestWebApi.Models
{
public class JwtConfig
{
/// <summary>
/// 密鑰
/// </summary>
public string SecretKey { get; set; }
/// <summary>
/// 發(fā)布者
/// </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>
/// 認(rèn)證用的密鑰
/// </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服務(wù)
/// </summary>
public void AddJwtService(IServiceCollection services)
{
services.AddAuthentication(option =>
{
//認(rèn)證middleware配置
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
//Token頒發(fā)機(jī)構(gòu)
ValidIssuer = JwtConfig.Issuer,
//頒發(fā)給誰
ValidAudience = JwtConfig.Audience,
//這里的key要進(jìn)行加密
IssuerSigningKey = JwtConfig.SymmetricSecurityKey,
//是否驗證Token有效期,使用當(dāng)前時間與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授權(quán)(數(shù)據(jù)將在請求頭中進(jìn)行傳輸) 在下方輸入Bearer {token} 即可,注意兩者之間有空格",
Name = "Authorization",//jwt默認(rèn)的參數(shù)名稱
In = ParameterLocation.Header,//jwt默認(rèn)存放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();//要在授權(quán)之前認(rèn)證,這個和[Authorize]特性有關(guān)
app.UseAuthorization();
app.MapControllers();
app.Run();
}
/// <summary>
/// 為了防止代碼過于臃腫,將新的配置放在這里寫
/// </summary>
/// <param name="builder"></param>
public static void AddMyService(WebApplicationBuilder builder)
{
#region 默認(rèn)的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標(biāo)題",
Description = $"API描述,v1版本"
});
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
//IncludeXmlComments 第二參數(shù) true 則顯示 控制器 注釋
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename), true);
JwtHelper.SwaggerAddJwtHeader(options);
});
#endregion
#region 添加Jwt服務(wù)
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中添加文章來源:http://www.zghlxwxcb.cn/news/detail-844995.html
"JwtConfig": {
"SecretKey": "lisheng741@qq.comlisheng741@qq.com",
"Issuer": "WebAppIssuer",
"Audience": "WebAppAudience",
"Expired": 30 // 過期時間(30min)
}
總結(jié)
Jwt其實也不是特別難,就是第一次配置的時候容易被繞暈。Jwt的策略我暫時先跳過了,對于解決普通問題一般來說已經(jīng)夠用了。文章來源地址http://www.zghlxwxcb.cn/news/detail-844995.html
到了這里,關(guān)于ASP.NET Core 8.0 WebApi 從零開始學(xué)習(xí)JWT登錄認(rèn)證的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!