淺談ASP.NET Core 中jwt授權(quán)認(rèn)證的流程原理
1,快速實(shí)現(xiàn)授權(quán)驗(yàn)證
什么是 JWT ?為什么要用 JWT ?JWT 的組成?
這些百度可以直接找到,這里不再贅述。
實(shí)際上,只需要知道 JWT 認(rèn)證模式是使用一段 Token 作為認(rèn)證依據(jù)的手段。
我們看一下 Postman 設(shè)置 Token 的位置。

那么,如何使用 C# 的 HttpClient 訪問一個 JWT 認(rèn)證的 WebAPI 呢?

下面來創(chuàng)建一個 ASP.NET Core 項(xiàng)目,嘗試添加 JWT 驗(yàn)證功能。
1.1 添加 JWT 服務(wù)配置
在 Startup.cs 的 ConfigureServices 方法中,添加一個服務(wù)
// 設(shè)置驗(yàn)證方式為 Bearer Token
// 你也可以添加 using Microsoft.AspNetCore.Authentication.JwtBearer;
// 使用 JwtBearerDefaults.AuthenticationScheme 代替 字符串 "Brearer"
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdABCD1234abcdABCD1234")), // 加密解密Token的密鑰
// 是否驗(yàn)證發(fā)布者
ValidateIssuer = true,
// 發(fā)布者名稱
ValidIssuer = "server",
// 是否驗(yàn)證訂閱者
// 訂閱者名稱
ValidateAudience = true,
ValidAudience = "client007",
// 是否驗(yàn)證令牌有效期
ValidateLifetime = true,
// 每次頒發(fā)令牌,令牌有效時間
ClockSkew = TimeSpan.FromMinutes(120)
};
});
修改 Configure 中的中間件
app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); // 注意這里 app.UseAuthorization();
就是這么簡單,通過以上設(shè)置,要求驗(yàn)證請求是否有權(quán)限。
1.2 頒發(fā) Token
頒發(fā)的 Token ,ASP.NET Core 不會保存。
ASP.NET Core 啟用了 Token 認(rèn)證,你隨便將生成 Token 的代碼放到不同程序的控制臺,只要密鑰和 Issuer 和 Audience 一致,生成的 Token 就可以登錄這個 ASP.NET Core。
也就是說,可以隨意創(chuàng)建控制臺程序生成 Token,生成的 Token 完全可以登錄 ASP.NET Core 程序。
至于原因,我們后面再說,
在 Program.cs 中,添加一個這樣的方法
static void ConsoleToke()
{
// 定義用戶信息
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, "癡者工良"),
new Claim(JwtRegisteredClaimNames.Email, "66666666666@qq.com"),
};
// 和 Startup 中的配置一致
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdABCD1234abcdABCD1234"));
JwtSecurityToken token = new JwtSecurityToken(
issuer: "server",
audience: "client007",
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
Console.WriteLine(jwtToken);
}
Main() 中,調(diào)用此方法
public static void Main(string[] args)
{
ConsoleToke();
CreateHostBuilder(args).Build().Run();
}
1.3 添加 API訪問
我們添加一個 API。
[Authorize] 特性用于標(biāo)識此 Controller 或 Action 需要使用合規(guī)的 Token 才能登錄。
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
public string Get()
{
Console.WriteLine(User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name));
return "訪問成功";
}
}
然后啟動 ASP.NET Core,在 Postman 測試 訪問 https://localhost/api/home。

發(fā)現(xiàn)報 401 (無權(quán)限)狀態(tài)碼,這是因?yàn)檎埱髸r不攜帶令牌,會導(dǎo)致不能訪問 API。
從控制臺終端復(fù)制生成的 Token 碼,復(fù)制到 Postman 中,再次訪問,發(fā)現(xiàn)響應(yīng)狀態(tài)碼為 200,響應(yīng)成功。

ASP.NET Core 自帶 jwt 認(rèn)證大概就是這樣。
那么,ASP.NET Core 內(nèi)部是如何實(shí)現(xiàn)的呢?又有哪些特性哪些坑呢?請往下看~
2,探究授權(quán)認(rèn)證中間件
在上面的操作中,我們在管道配置了兩個中間件。
app.UseAuthentication(); app.UseAuthorization();
app.UseAuthentication(); 的作用是通過 ASP.NET Core 中配置的授權(quán)認(rèn)證,讀取客戶端中的身份標(biāo)識(Cookie,Token等)并解析出來,存儲到 context.User 中。
app.UseAuthorization(); 的作用是判斷當(dāng)前訪問 Endpoint (Controller或Action)是否使用了 [Authorize]以及配置角色或策略,然后校驗(yàn) Cookie 或 Token 是否有效。
使用特性設(shè)置相應(yīng)通過認(rèn)證才能訪問,一般有以下情況。
// 不適用特性,可以直接訪問
public class AController : ControllerBase
{
public string Get() { return "666"; }
}
/// <summary>
/// 整個控制器都需要授權(quán)才能訪問
/// </summary>
[Authorize]
public class BController : ControllerBase
{
public string Get() { return "666"; }
}
public class CController : ControllerBase
{
// 只有 Get 需要授權(quán)
[Authorize]
public string Get() { return "666"; }
public string GetB() { return "666"; }
}
/// <summary>
/// 整個控制器都需要授權(quán),但 Get 不需要
/// </summary>
[Authorize]
public class DController : ControllerBase
{
[AllowAnonymous]
public string Get() { return "666"; }
}
2.1 實(shí)現(xiàn) Token 解析
至于 ASP.NET Core 中,app.UseAuthentication(); 和 app.UseAuthorization(); 的源代碼各種使用了一個項(xiàng)目來寫,代碼比較多。要理解這兩個中間件的作用,我們不妨來手動實(shí)現(xiàn)他們的功能。
解析出的 Token 是一個 ClaimsPrincipal 對象,將此對象給 context.User 賦值,然后在 API 中可以使用 User 實(shí)例來獲取用戶的信息。
在中間件中,使用下面的代碼可以獲取客戶端請求的 Token 解析。
context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, JwtBearerDefaults.AuthenticationScheme);
那么,我們?nèi)绾问止脑?Http 請求中,解析出來呢?且看我慢慢來分解步驟。
首先創(chuàng)建一個 TestMiddleware 文件,作為中間件使用。
public class TestMiddleware
{
private readonly RequestDelegate _next;
jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
public TestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// 我們寫代碼的區(qū)域
// 我們寫代碼的區(qū)域
await _next(context);
}
}
2.1.1 從 Http 中獲取 Token
下面代碼可以中 http 請求中,取得頭部的 Token 。
當(dāng)然,客戶端可能沒有攜帶 Token,可能獲取結(jié)果為 null ,自己加個判斷。
貼到代碼區(qū)域。
string tokenStr = context.Request.Headers["Authorization"].ToString();
Header 的 Authorization 鍵,是由 Breaer {Token}組成的字符串。
2.1.2 判斷是否為有效令牌
拿到 Token 后,還需要判斷這個 Token 是否有效。
因?yàn)?Authorization 是由 Breaer {Token}組成,所以我們需要去掉前面的 Brear 才能獲取 Token。
/// <summary>
/// Token是否是符合要求的標(biāo)準(zhǔn) Json Web 令牌
/// </summary>
/// <param name="tokenStr"></param>
/// <returns></returns>
public bool IsCanReadToken(ref string tokenStr)
{
if (string.IsNullOrWhiteSpace(tokenStr) || tokenStr.Length < 7)
return false;
if (!tokenStr.Substring(0, 6).Equals(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme))
return false;
tokenStr = tokenStr.Substring(7);
bool isCan = jwtSecurityTokenHandler.CanReadToken(tokenStr);
return isCan;
}
獲得 Token 后,通過 JwtSecurityTokenHandler.CanReadToken(tokenStr); 來判斷 Token 是否符合協(xié)議規(guī)范。
將下面判斷貼到代碼區(qū)域。
if (!IsCanReadToken(ref tokenStr))
return ;
2.1.3 解析 Token
下面代碼可以將 Header 的 Authorization 內(nèi)容轉(zhuǎn)為 JwtSecurityToken 對象。
(截取字符串的方式很多種,喜歡哪個就哪個。。。)
/// <summary>
/// 從Token解密出JwtSecurityToken,JwtSecurityToken : SecurityToken
/// </summary>
/// <param name="tokenStr"></param>
/// <returns></returns>
public JwtSecurityToken GetJwtSecurityToken(string tokenStr)
{
var jwt = jwtSecurityTokenHandler.ReadJwtToken(tokenStr);
return jwt;
}
不過這個 GetJwtSecurityToken 不是我們關(guān)注的內(nèi)容,我們是要獲取 Claim。
JwtSecurityToken.Claims
將下面代碼貼到代碼區(qū)域
JwtSecurityToken jst = GetJwtSecurityToken(tokenStr); IEnumerable<Claim> claims = jst.Claims;
2.1.4 生成 context.User
context.User 是一個 ClaimsPrincipal 類型,我們通過解析出的 Claim,生成 ClaimsPrincipal。
JwtSecurityToken jst = GetJwtSecurityToken(tokenStr);
IEnumerable<Claim> claims = jst.Claims;
List<ClaimsIdentity> ci = new List<ClaimsIdentity>() { new ClaimsIdentity(claims) };
context.User = new ClaimsPrincipal(ci);
最終的代碼塊是這樣的
// 我們寫代碼的區(qū)域
string tokenStr = context.Request.Headers["Authorization"].ToString();
string requestUrl = context.Request.Path.Value;
if (!IsCanReadToken(ref tokenStr))
return;
JwtSecurityToken jst = GetJwtSecurityToken(tokenStr);
IEnumerable<Claim> claims = jst.Claims;
List<ClaimsIdentity> ci = new List<ClaimsIdentity>() { new ClaimsIdentity(claims) };
context.User = new ClaimsPrincipal(ci);
var x = new ClaimsPrincipal(ci);
// 我們寫代碼的區(qū)域
2.2 實(shí)現(xiàn)校驗(yàn)認(rèn)證
app.UseAuthentication(); 的大概實(shí)現(xiàn)過程已經(jīng)做出了說明,現(xiàn)在我們來繼續(xù)實(shí)現(xiàn) app.UseAuthorization(); 中的功能。
繼續(xù)使用上面的中間件,在原代碼塊區(qū)域添加新的區(qū)域。
// 我們寫代碼的區(qū)域 // 我們寫的代碼塊
22.2.1 Endpoint
Endpoint 標(biāo)識了一個 http 請求所訪問的路由信息和 Controller 、Action 及其特性等信息。
[Authorize] 特性繼承了 IAuthorizeData。[AllowAnonymous] 特性繼承了 IAllowAnonymous。
以下代碼可以獲取所訪問的節(jié)點(diǎn)信息。
var endpoint = context.GetEndpoint();
那么如何判斷所訪問的 Controller 和 Action 是否使用了認(rèn)證相關(guān)的特性?
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
Metadata 是一個 ASP.NET Core 實(shí)現(xiàn)的集合對象,GetOrderedMetadata<T> 可以找出需要的特性信息。
這個集合不會區(qū)分是 Contrller 還是 Action 的 [Authorize] 特性。
那么判斷 是否有 [AllowAnonymous] 特性,可以這樣使用。
if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
{
await _next(context);
return;
}
到此這篇關(guān)于淺談ASP.NET Core 中jwt授權(quán)認(rèn)證的流程原理的文章就介紹到這了,更多相關(guān)ASP.NET Core jwt授權(quán)認(rèn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ASP.NET web.config中數(shù)據(jù)庫連接字符串connectionStrings節(jié)的配置方法
ASP.NET web.config中數(shù)據(jù)庫連接字符串connectionStrings節(jié)的配置方法,需要的朋友可以參考一下2013-05-05
.NET Core 遷移躺坑記續(xù)集之Win下莫名其妙的超時
這篇文章主要介紹了.NET Core 遷移躺坑記續(xù)集之Win下莫名其妙的超時,本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-04-04
aspx文件格式使用URLRewriter實(shí)現(xiàn)靜態(tài)化變成html
如何隱藏aspx文件格式,變成html,使用asp.net 開發(fā)的網(wǎng)頁程序,使用URLRewriter.dll 實(shí)現(xiàn)靜態(tài)化,接下來將介紹下具體操作步驟,感興趣的朋友可以參考下2013-04-04
基于.net standard 的動態(tài)編譯實(shí)現(xiàn)代碼
這篇文章主要介紹了基于.net standard 的動態(tài)編譯實(shí)現(xiàn)代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-07-07
.NET中如何將文本文件的內(nèi)容存儲到DataSet
大家在項(xiàng)目中比較多的會對文件進(jìn)行操作,例如文件的上傳下載,文件的壓縮和解壓等IO操作。而在.NET項(xiàng)目中較多的會使用DataSet,DataTable進(jìn)行數(shù)據(jù)的緩存。每一個DataSet都是一個或多個DataTable對象的集合,本文主要介紹的是如何將文本文件的內(nèi)容存儲到DataSet里去。2016-12-12
Entity Framework使用Code First模式管理視圖
本文詳細(xì)講解了Entity Framework使用Code First模式管理視圖的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
asp.net EXECUTENONQUERY()返回值介紹
前些日子作一些數(shù)據(jù)項(xiàng)目的時候 在ADO.NET 中處理 ExecuteNonQuery()方法時,總是通過判斷其返回值是否大于0來判斷操作時候成功 。但是實(shí)際上并不是這樣的,下面詳細(xì)介紹一下,有需要的朋友可以參考2013-08-08

