asp net core2.1如何使用jwt從原理到精通(二)
在aspnet core中,自定義jwt管道驗(yàn)證
有了上一節(jié)的內(nèi)容作為基礎(chǔ),那這點(diǎn)也是非常容易的,關(guān)鍵點(diǎn)在中間件,只是把上一級(jí)在測(cè)試類中的自定義驗(yàn)證放到中間件中來(lái)即可,
不過(guò)需要注意:中間件 的位置很重要,只有它后面的管道才會(huì)收到影響;
那我們先建一個(gè)自定義中間件類:(中間件的詳細(xì)內(nèi)容這里就不講了,大家可以參考官網(wǎng)和其他博文)
/// <summary>
/// 自定義授權(quán)中間件
/// </summary>
public class JwtCustomerAuthorizeMiddleware
{
private readonly RequestDelegate next;
public JwtCustomerAuthorizeMiddleware(RequestDelegate next, string secret, List<string> anonymousPathList)
{
#region 設(shè)置自定義jwt 的秘鑰
if(!string.IsNullOrEmpty(secret))
{
TokenContext.securityKey = secret;
}
#endregion
this.next = next;
UserContext.AllowAnonymousPathList.AddRange(anonymousPathList);
}
public async Task Invoke(HttpContext context, UserContext userContext,IOptions<JwtOption> optionContainer)
{
if (userContext.IsAllowAnonymous(context.Request.Path))
{
await next(context);
return;
}
var option = optionContainer.Value;
#region 身份驗(yàn)證,并設(shè)置用戶Ruser值
var result = context.Request.Headers.TryGetValue("Authorization", out StringValues authStr);
if (!result || string.IsNullOrEmpty(authStr.ToString()))
{
throw new UnauthorizedAccessException("未授權(quán)");
}
result = TokenContext.Validate(authStr.ToString().Substring("Bearer ".Length).Trim(), payLoad =>
{
var success = true;
//可以添加一些自定義驗(yàn)證,用法參照測(cè)試用例
//驗(yàn)證是否包含aud 并等于 roberAudience
success = success && payLoad["aud"]?.ToString() == option.Audience;
if (success)
{
//設(shè)置Ruse值,把user信息放在payLoad中,(在獲取jwt的時(shí)候把當(dāng)前用戶存放在payLoad的ruser鍵中)
//如果用戶信息比較多,建議放在緩存中,payLoad中存放緩存的Key值
userContext.TryInit(payLoad["ruser"]?.ToString());
}
return success;
});
if (!result)
{
throw new UnauthorizedAccessException("未授權(quán)");
}
#endregion
#region 權(quán)限驗(yàn)證
if (!userContext.Authorize(context.Request.Path))
{
throw new UnauthorizedAccessException("未授權(quán)");
}
#endregion
await next(context);
}
}
上面這個(gè)中間件中有個(gè)UserContext上線文,這個(gè)類主要管理當(dāng)前用戶信息和權(quán)限,其他信息暫時(shí)不管;我們先看一下這個(gè)中間件的驗(yàn)證流程如何:
該中間件主要是針對(duì)訪問(wèn)的路徑進(jìn)行驗(yàn)證,當(dāng)然你也可以針對(duì)其他信息進(jìn)行驗(yàn)證,比如(控制器名稱,動(dòng)作名稱,等)
- 檢查當(dāng)前url是否可以匿名訪問(wèn),如果可以就直接通過(guò),不做驗(yàn)證了;如果不是可以匿名訪問(wèn)的路徑,那就繼續(xù)
- 獲取當(dāng)前http頭部攜帶的jwt(存放在頭部的 Authorization中);
- 使用上一節(jié)的講的TokenContext做必須的驗(yàn)證和自定義復(fù)雜驗(yàn)證;
- 獲取當(dāng)前訪問(wèn)用戶信息,我們把用戶的基本信息放在payLoad["ruser"]中,請(qǐng)看代碼如何操作
- 到這里為止,都是做的身份驗(yàn)證,表明你是一個(gè)有身份的的人;接下來(lái)是做權(quán)限驗(yàn)證,你是一個(gè)有身份的人,并不代表你是一個(gè)隨便到處訪問(wèn)的人;你能訪問(wèn)哪些url或者action,就要得到權(quán)限驗(yàn)證的認(rèn)可
- 我們把權(quán)限驗(yàn)證放到 userContext.Authorize方法中(這里怎么操作,這里就不深入講解,基本原理是從數(shù)據(jù)庫(kù)或者緩存中獲取當(dāng)前用戶對(duì)應(yīng)的權(quán)限列表,也就是url列表,進(jìn)行對(duì)比);
自定義中間件使用jwt驗(yàn)證就這些內(nèi)容,是不是感覺(jué)很清晰,很簡(jiǎn)單,有木有;
中間已經(jīng)完成了,那接下來(lái)我們來(lái)使用它,我們?cè)賡tartup中的Configure方法中添加如下代碼
app.UseMiddleware<JwtCustomerAuthorizeMiddleware>(Configuration["JwtOption:SecurityKey"], new List<string>() { "/api/values/getjwt","/" });
當(dāng)然上面可匿名訪問(wèn)的url也可以定義在appsetting.json文件中,可以自行嘗試
如何通過(guò)自定義策略形式實(shí)現(xiàn)自定義jwt驗(yàn)證
創(chuàng)建自定義策略的詳細(xì)介紹可以參考官網(wǎng),這里就不詳細(xì)介紹,
首先我們上代碼,創(chuàng)建自定義策略非常重要的兩個(gè)類,如下:
public class CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>
{
/// <summary>
/// 常用自定義驗(yàn)證策略,模仿自定義中間件JwtCustomerauthorizeMiddleware的驗(yàn)證范圍
/// </summary>
/// <param name="context"></param>
/// <param name="requirement"></param>
/// <returns></returns>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CommonAuthorize requirement)
{
var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext;
var userContext = httpContext.RequestServices.GetService(typeof(UserContext)) as UserContext;
var jwtOption = (httpContext.RequestServices.GetService(typeof(IOptions<JwtOption>)) as IOptions<JwtOption>).Value;
#region 身份驗(yàn)證,并設(shè)置用戶Ruser值
var result = httpContext.Request.Headers.TryGetValue("Authorization", out StringValues authStr);
if (!result || string.IsNullOrEmpty(authStr.ToString()))
{
return Task.CompletedTask;
}
result = TokenContext.Validate(authStr.ToString().Substring("Bearer ".Length).Trim(), payLoad =>
{
var success = true;
//可以添加一些自定義驗(yàn)證,用法參照測(cè)試用例
//驗(yàn)證是否包含aud 并等于 roberAudience
success = success && payLoad["aud"]?.ToString() == jwtOption.Audience;
if (success)
{
//設(shè)置Ruse值,把user信息放在payLoad中,(在獲取jwt的時(shí)候把當(dāng)前用戶存放在payLoad的ruser鍵中)
//如果用戶信息比較多,建議放在緩存中,payLoad中存放緩存的Key值
userContext.TryInit(payLoad["ruser"]?.ToString());
}
return success;
});
if (!result)
{
return Task.CompletedTask;
}
#endregion
#region 權(quán)限驗(yàn)證
if (!userContext.Authorize(httpContext.Request.Path))
{
return Task.CompletedTask;
}
#endregion
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class CommonAuthorize: IAuthorizationRequirement
{
}
其中兩個(gè)重要的類是哪兩個(gè)呢?他們的作用又是什么呢?
1、CommonAuthorize: IAuthorizationRequirement,至于取什么名字,自己定義,但必須繼承IAuthorizationRequirement,這類主要是承載一些初始化值,讓后傳遞到Handler中去,給驗(yàn)證做邏輯運(yùn)算提供一些可靠的信息;我這里是空的;自己根據(jù)自身情況自己定義適當(dāng)?shù)膶傩宰鳛槌跏紨?shù)據(jù)的承載容器;
2、CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>這個(gè)是重點(diǎn),承載了驗(yàn)證的邏輯運(yùn)算
需要重寫override Task HandleRequirementAsync方法,所有的邏輯都在該方法中,他的主要邏輯和上面的自定義中間件很相似,只少了上面的第一步;驗(yàn)證流程如下:
- 獲取當(dāng)前http頭部攜帶的jwt(存放在頭部的 Authorization中);
- 使用上一節(jié)的講的TokenContext做必須的驗(yàn)證和自定義復(fù)雜驗(yàn)證;
- 獲取當(dāng)前訪問(wèn)用戶信息,我們把用戶的基本信息放在payLoad["ruser"中,請(qǐng)看代碼如何操作
- 到這里為止,都是做的身份驗(yàn)證,表明你是一個(gè)有身份的的人;接下來(lái)是做權(quán)限驗(yàn)證,你是一個(gè)有身份的人,并不代表你是一個(gè)隨便到處訪問(wèn)的人;你能訪問(wèn)哪些url或者action,就要得到權(quán)限驗(yàn)證的認(rèn)可
- 我們把權(quán)限驗(yàn)證放到 userContext.Authorize方法中(這里怎么操作,這里就不深入講解,基本原理是從數(shù)據(jù)庫(kù)或者緩存中獲取當(dāng)前用戶對(duì)應(yīng)的權(quán)限列表,也就是url列表,進(jìn)行對(duì)比);
context.Succeed(requirement);是驗(yàn)證成功,如果沒(méi)有這個(gè),就默認(rèn)驗(yàn)證失敗
因?yàn)閁serContext把負(fù)責(zé)了權(quán)限驗(yàn)證,所以不會(huì)把流程搞得感覺(jué)很亂,并且可以重用,至于用那種形式驗(yàn)證也很容易切換
3、是不是很簡(jiǎn)單,和自定義管道驗(yàn)證的的代碼幾乎一模一樣,
如何使用自定義定義策略呢?
1、在startup類中的ConfigureServices中加入如下代碼:
services.AddAuthorization(option =>
{
#region 自定義驗(yàn)證策略
option.AddPolicy("common", policy => policy.Requirements.Add(new CommonAuthorize()));
#endregion
}).AddAuthentication(option =>
{
option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(option =>
{
if (!string.IsNullOrEmpty(config["JwtOption:SecurityKey"]))
{
TokenContext.securityKey = config["JwtOption:SecurityKey"];
}
//設(shè)置需要驗(yàn)證的項(xiàng)目
option.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenContext.securityKey))//拿到SecurityKey
};
});
//自定義策略IOC添加
services.AddSingleton<IAuthorizationHandler, CommonAuthorizeHandler>();
以上代碼主要分3個(gè)部分
1、添加上面自定義的策略,并取名;
2、設(shè)置秘鑰,這個(gè)秘鑰就是上一節(jié)中生成jwt的秘鑰,必須要要一樣,否則是簽名不正確
3、注入上面建立的一個(gè)重要類CommonAuthorizeHandler,如上面代碼
2、在startup類中的Configure中添加 app.UseAuthentication();
3、在需要驗(yàn)證的Controller或者Action中加上[Authorize(Policy = "common")]屬性,看下圖:
到此為止你就可以使用自定義策略的驗(yàn)證了;
使用管道和自定義策略兩種形式進(jìn)行驗(yàn)證有什么區(qū)別呢?
從效果上看都是一樣的,稍微有點(diǎn)區(qū)別
- 使用管道的方式,感覺(jué)方便點(diǎn),清晰點(diǎn)
- 使用自定義策略的方式,效率稍微高一點(diǎn),畢竟不是所有的請(qǐng)求都會(huì)進(jìn)行是否可以匿名訪問(wèn)運(yùn)算和建立管道的消耗,只有加入Authorize屬性的Controller和Action的才會(huì)進(jìn)入;當(dāng)然這點(diǎn)損耗可以忽略不計(jì),看自己的喜好;
至于你喜歡那種,就使用哪種吧,性能可以忽略不計(jì);
不管使用哪種方式使用jwt作為身份和權(quán)限驗(yàn)證是不是很簡(jiǎn)單,關(guān)鍵這里也把權(quán)限驗(yàn)證的邏輯抽出來(lái)了,這樣代碼就更清晰明了了;
至于Authorize的屬性形式,還有很多其他的策略,比如用戶、申明,角色等,可查看官網(wǎng)https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/?view=aspnetcore-2.0
下一章將講解 用戶,申明 ,角色的驗(yàn)證,并這些怎么在自定義的驗(yàn)證中實(shí)現(xiàn),以便大家對(duì)他有個(gè)清晰的對(duì)比
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
.net中 發(fā)送郵件內(nèi)容嵌入圖片的具體實(shí)例
這篇文章主要介紹了.net中 發(fā)送郵件內(nèi)容嵌入圖片的具體實(shí)例,需要的朋友可以參考下2014-02-02
ASP.NET?MVC實(shí)現(xiàn)單個(gè)圖片上傳、限制圖片格式與大小并在服務(wù)端裁剪圖片
這篇文章介紹了ASP.NET?MVC實(shí)現(xiàn)單個(gè)圖片上傳、限制圖片格式與大小并在服務(wù)端裁剪圖片的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09
Entity?Framework實(shí)體拆分多個(gè)表
這篇文章介紹了Entity?Framework實(shí)體拆分多個(gè)表的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
asp.net 從客戶端中檢測(cè)到有潛在危險(xiǎn)的 Request.Form 值錯(cuò)誤解
asp.net程序運(yùn)行時(shí)出現(xiàn)以下錯(cuò)誤: “/news”應(yīng)用程序中的服務(wù)器錯(cuò)誤。2009-05-05
VS2010制作第一個(gè)簡(jiǎn)單網(wǎng)站
這篇文章主要為大家介紹VS2010如何制作第一個(gè)簡(jiǎn)單網(wǎng)站,和大家一起學(xué)習(xí) 動(dòng)態(tài)網(wǎng)站開(kāi)發(fā)技術(shù),感興趣的小伙伴們可以參考一下2015-11-11
在.net core中實(shí)現(xiàn)字段和屬性注入的示例代碼
這篇文章主要介紹了在.net core中實(shí)現(xiàn)字段和屬性注入的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
.net重啟iis線程池和iis站點(diǎn)程序代碼分享
服務(wù)器監(jiān)控,一定時(shí)間內(nèi)或者iis異常就需要重啟線程池和站點(diǎn),下面我們用代碼來(lái)做這個(gè)功能2013-12-12

