您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“ASP.NET Core 6.0怎么添加JWT認(rèn)證和授權(quán)功能”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
Authentication 和 Authorization 長得很像,傻傻分不清楚。
Authentication(認(rèn)證):標(biāo)識用戶的身份,一般發(fā)生在登錄的時候。
Authorization(授權(quán)):授予用戶權(quán)限,指定用戶能訪問哪些資源;授權(quán)的前提是知道這個用戶是誰,所以授權(quán)必須在認(rèn)證之后。
安裝相關(guān) Nuget 包:Microsoft.AspNetCore.Authentication.JwtBearer
準(zhǔn)備配置信息(密鑰等)
添加服務(wù)
調(diào)用中間件
實現(xiàn)一個 JwtHelper,用于生成 Token
控制器限制訪問(添加 Authorize 標(biāo)簽)
安裝 Microsoft.AspNetCore.Authentication.JwtBearer
在程序包管理器控制臺中:
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 6.0.1
在 appsetting.json 中,添加一個 Jwt 節(jié)點
"Jwt": { "SecretKey": "lisheng741@qq.com", "Issuer": "WebAppIssuer", "Audience": "WebAppAudience" }
在 Program.cs 文件中注冊服務(wù)。
// 引入所需的命名空間 using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; // …… var configuration = builder.Configuration; // 注冊服務(wù) builder.Services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, //是否驗證Issuer ValidIssuer = configuration["Jwt:Issuer"], //發(fā)行人Issuer ValidateAudience = true, //是否驗證Audience ValidAudience = configuration["Jwt:Audience"], //訂閱人Audience ValidateIssuerSigningKey = true, //是否驗證SecurityKey IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"])), //SecurityKey ValidateLifetime = true, //是否驗證失效時間 ClockSkew = TimeSpan.FromSeconds(30), //過期時間容錯值,解決服務(wù)器端時間不同步問題(秒) RequireExpirationTime = true, }; });
調(diào)用 UseAuthentication(認(rèn)證),必須在所有需要身份認(rèn)證的中間件前調(diào)用,比如 UseAuthorization(授權(quán))。
// …… app.UseAuthentication(); app.UseAuthorization(); // ……
主要是用于生成 JWT 的 Token。
using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace TestWebApi; public class JwtHelper { private readonly IConfiguration _configuration; public JwtHelper(IConfiguration configuration) { _configuration = configuration; } public string CreateToken() // 1. 定義需要使用到的Claims var claims = new[] { new Claim(ClaimTypes.Name, "u_admin"), //HttpContext.User.Identity.Name new Claim(ClaimTypes.Role, "r_admin"), //HttpContext.User.IsInRole("r_admin") new Claim(JwtRegisteredClaimNames.Jti, "admin"), new Claim("Username", "Admin"), new Claim("Name", "超級管理員") }; // 2. 從 appsettings.json 中讀取SecretKey var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"])); // 3. 選擇加密算法 var algorithm = SecurityAlgorithms.HmacSha256; // 4. 生成Credentials var signingCredentials = new SigningCredentials(secretKey, algorithm); // 5. 根據(jù)以上,生成token var jwtSecurityToken = new JwtSecurityToken( _configuration["Jwt:Issuer"], //Issuer _configuration["Jwt:Audience"], //Audience claims, //Claims, DateTime.Now, //notBefore DateTime.Now.AddSeconds(30), //expires signingCredentials //Credentials ); // 6. 將token變?yōu)閟tring var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); return token; }
該 JwtHelper 依賴于 IConfiguration(為了讀取配置文件),將 JwtHelper 的創(chuàng)建交由 DI 容器,在 Program.cs 中添加服務(wù):
var configuration = builder.Configuration; builder.Services.AddSingleton(new JwtHelper(configuration));
將 JwtHelper 注冊為單例模式。
新建一個 AccountController,以構(gòu)造函數(shù)方式注入 JwtHelper,添加兩個 Action:GetToken 用于獲取 Token,GetTest 打上 [Authorize] 標(biāo)簽用于驗證認(rèn)證。
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace TestWebApi.Controllers; [Route("api/[controller]/[action]")] [ApiController] public class AccountController : ControllerBase { private readonly JwtHelper _jwtHelper; public AccountController(JwtHelper jwtHelper) { _jwtHelper = jwtHelper; } [HttpGet] public ActionResult<string> GetToken() { return _jwtHelper.CreateToken(); } [Authorize] [HttpGet] public ActionResult<string> GetTest() { return "Test Authorize"; } }
方式一:通過 Postman、Apifox 等接口調(diào)試軟件調(diào)試
使用 Postman 調(diào)用 /api/Account/GetToken 生成 Token
在調(diào)用 /api/Account/GetTest 時傳入 Token,得到返回結(jié)果
方式二:在瀏覽器控制臺調(diào)試
調(diào)試 /api/Account/GetToken
var xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function() { if(this.readyState === 4) { console.log(token = this.responseText); //這里用了一個全局變量 token,為下一個接口服務(wù) } }); xhr.open("GET", "/api/Account/GetToken"); xhr.send();
調(diào)試 /api/Account/GetTest
var xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function() { if(this.readyState === 4) { console.log(this.status, this.responseText); //this.status為響應(yīng)狀態(tài)碼,401為無認(rèn)證狀態(tài) } }); xhr.open("GET", "/api/Account/GetTest"); xhr.setRequestHeader("Authorization",`Bearer ${token}`); //附帶上 token xhr.send();
注意:授權(quán)必須基于認(rèn)證,即:若沒有完成上文關(guān)于認(rèn)證的配置,則下面的授權(quán)是不會成功的。
授權(quán)部分,將先介紹相關(guān)標(biāo)簽、授權(quán)方式,再介紹基于策略的授權(quán)。這三部分大致的內(nèi)容如下描述:
相關(guān)標(biāo)簽:Authorize 和 AllowAnonymous
授權(quán)方式:介紹 Policy、Role、Scheme 的基本內(nèi)容
基于策略(Policy)的授權(quán):深入 Policy 授權(quán)方式
授權(quán)相關(guān)標(biāo)簽具體請查考官方文檔簡單授權(quán)
[Authorize]
打上該標(biāo)簽的 Controller 或 Action 必須經(jīng)過認(rèn)證,且可以標(biāo)識需要滿足哪些授權(quán)規(guī)則。
授權(quán)規(guī)則可以是 Policy(策略)、Roles(角色) 或 AuthenticationSchemes(方案)。
[Authorize(Policy = "", Roles ="", AuthenticationSchemes ="")]
[AllowAnonymous]
允許匿名訪問,級別高于 [Authorize] ,若兩者同時作用,將生效 [AllowAnonymous]
基本上授權(quán)只有:Policy、Role、Scheme 這3種方式,對應(yīng) Authorize 標(biāo)簽的3個屬性。
推薦的授權(quán)方式,在 ASP.NET Core 的官方文檔提及最多的。一個 Policy 可以包含多個要求(要求可能是 Role 匹配,也可能是 Claims 匹配,也可能是其他方式。)
下面舉個基礎(chǔ)例子(說是基礎(chǔ)例子,主要是基于 Policy 的授權(quán)方式可以不斷深入追加一些配置):
在 Program.cs 中,添加兩條 Policy:
policy1 要求用戶擁有一個 Claim,其 ClaimType 值為 EmployeeNumber。
policy2 要求用戶擁有一個 Claim,其 ClaimType 值為 EmployeeNumber,且其 ClaimValue 值為1、2、3、4 或 5。
builder.Services.AddAuthorization(options => { options.AddPolicy("policy1", policy => policy.RequireClaim("EmployeeNumber")); options.AddPolicy("policy2", policy => policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5")); })
在控制器中添加 [Authorize] 標(biāo)簽即可生效:
[Authorize(Policy = "policy1")] public class TestController : ControllerBase
或在控制器的 Action 上:
public class TestController : ControllerBase { [Authorize(Policy = "policy1")] public ActionResult<string> GetTest => "GetTest"; }
基于角色授權(quán),只要用戶擁有角色,即可通過授權(quán)驗證。
在認(rèn)證時,給用戶添加角色相關(guān)的 Claim ,即可標(biāo)識用戶擁有的角色(注:一個用戶可以擁有多個角色的 Claim),如:
new Claim(ClaimTypes.Role, "admin"), new Claim(ClaimTypes.Role, "user")
在 Controller 或 Action 中:
[Authorize(Roles = "user")] public class TestController : ControllerBase { public ActionResult<string> GetUser => "GetUser"; [Authorize(Roles = "admin")] //與控制器的Authorize疊加作用,除了擁有user,還需擁有admin public ActionResult<string> GetAdmin => "GetAdmin"; [Authorize(Roles = "user,admin")] //user 或 admin 其一滿足即可 public ActionResult<string> GetUserOrAdmin => "GetUserOrAdmin"; }
方案如:Cookies 和 Bearer,當(dāng)然也可以是自定義的方案。
由于這種方式不常用,這里不做展開,請參考官方文檔按方案限制標(biāo)識。
基于策略(Policy)的授權(quán)
上面已經(jīng)提及了一個基于策略授權(quán)的基礎(chǔ)例子,下面將繼續(xù)深入這種授權(quán)方式。
1 授權(quán)過程
在不斷深入 Policy 這種方式的授權(quán)之前,有必要將授權(quán)的過程描述一下。授權(quán)過程描述建議結(jié)合源碼查看,這樣能更清楚其中的作用。當(dāng)然,這一部分是比較難懂,筆者表述可能也不夠清晰,而這一部分對于完成授權(quán)的配置也不會有影響,故而如果讀者看不明白或無法理解,可以暫且跳過,不必糾結(jié)。
建議看一下ASP.NET Core使用JWT認(rèn)證授權(quán)的方法這篇文章,文章將授權(quán)相關(guān)的源碼整理出來了,并說明了其中的關(guān)系。
這里簡單梳理一下:
與授權(quán)相關(guān)的 interface 和 class 如下:
IAuthorizationService #驗證授權(quán)的服務(wù),主要方法AuthorizeAsync DefaultAuthorizationService #IAuthorizationService的默認(rèn)實現(xiàn) IAuthorizationHandler #負(fù)責(zé)檢查是否滿足要求,主要方法HandleAsync IAuthorizationRequirement #只有屬性,沒有方法;用于標(biāo)記服務(wù),以及用于追蹤授權(quán)是否成功的機(jī)制。 AuthorizationHandler<TRequirement> #主要方法HandleRequirementAsync
這些 interface 和 class 的關(guān)系以及授權(quán)過程是這樣的:
DefaultAuthorizationService
實現(xiàn) IAuthorizationService
的 AuthorizeAsync
方法。
AuthorizeAsync
方法會獲取到所有實現(xiàn)了 IAuthorizationHandler
的實例,并循環(huán)調(diào)用所有實例的 HandleAsync 方法檢查是否滿足授權(quán)要求,如果有任一一個 HandleAsync
返回了 Fail 則將結(jié)束循環(huán)(細(xì)節(jié)請參考官方文檔處理程序返回結(jié)果),并禁止用戶訪問。
IAuthorizationHandler
的作用如上一點所述,提供了一個 HandleAsync
方法,用于檢查授權(quán)。
IAuthorizationRequirement
是一個要求,主要是配合 AuthorizationHandler<TRequirement>
使用。
AuthorizationHandler<TRequirement>
實現(xiàn)了 IAuthorizationHandler
的 HandleAsync
方法,并提供了一個 HandleRequirementAsync
的方法。HandleRequirementAsync
用于檢查 Requirement
(要求)是否滿足。HandleAsync
的默認(rèn)實現(xiàn)為獲取所有實現(xiàn) TRequirement
的請求(且該請求由 Policy 添加進(jìn)列表里),循環(huán)調(diào)用 HandleRequirementAsync
,檢查哪個要求(Requirement)能滿足授權(quán)。
簡述一下:
[Authorize]
標(biāo)簽生效時,調(diào)用的是 IAuthorizationService
的 AuthorizeAsync
(由 DefaultAuthorizationService
實現(xiàn))。
AuthorizeAsync
會去調(diào)用所有 IAuthorizationHandler
的 HandleAsync
(由 AuthorizationHandler<TRequirement>
實現(xiàn))。
HandleAsync
會去調(diào)用 AuthorizationHandler<TRequirement>
的HandleRequirementAsync
的方法。
注意:這里只是列出了主要的接口和類,部分沒有列出,如:IAuthorizationHandlerProvider
(這個接口的默認(rèn)實現(xiàn) DefaultAuthorizationHandlerProvider
,主要是用于收集 IAuthorizationHandler
并返回 IEnumerable<IAuthorizationHandler>
)
2 實現(xiàn)說明
IAuthorizationService
已默認(rèn)實現(xiàn),不需要我們做額外工作。
IAuthorizationHandler
由 AuthorizationHandler<TRequirement>
實現(xiàn)。
所以我們要做的,是:
第一步,準(zhǔn)備 Requirement 實現(xiàn) IAuthorizationRequirement
第二步,添加一個 Handler 程序繼承 AuthorizationHandler<TRequirement>
并重寫 HandleRequirementAsync
方法
在實現(xiàn) Requirement 之前,我們需要先定義一些權(quán)限項,主要用于后續(xù)作為 Policy 的名稱,并傳入 我們實現(xiàn)的 Requirement 之中。
public static class UserPermission { public const string User = "User"; public const string UserCreate = User + ".Create"; public const string UserDelete = User + ".Delete"; public const string UserUpdate = User + ".Update"; }
如上,定義了“增”、“刪”、“改”等權(quán)限,其中 User 將擁有完整權(quán)限。
public class PermissionAuthorizationRequirement : IAuthorizationRequirement { public PermissionAuthorizationRequirement(string name) { Name = name; } public string Name { get; set; } }
使用 Name 屬性表示權(quán)限的名稱,與 UserPermission 中的常量對應(yīng)。
這里假定用戶的 Claim 中 ClaimType 為 Permission 的項,如:
new Claim("Permission", UserPermission.UserCreate), new Claim("Permission", UserPermission.UserUpdate)
如上,標(biāo)識該用戶用戶 UserCreate 和 UserUpdate 的權(quán)限。
注意:當(dāng)然,實際程序我們肯定不是這樣實現(xiàn)的,這里只是簡易示例。
接著,實現(xiàn)一個授權(quán) Handler:
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionAuthorizationRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement) { var permissions = context.User.Claims.Where(_ => _.Type == "Permission").Select(_ => _.Value).ToList(); if (permissions.Any(_ => _.StartsWith(requirement.Name))) { context.Succeed(requirement); } return Task.CompletedTask; } }
運行 HandleRequirementAsync
時,會將用戶的 Claim 中 ClaimType 為 Permission 的項取出,并獲取其 Value 組成一個 List<string>
。
接著驗證 Requirement 是否滿足授權(quán),滿足則運行 context.Succeed
。
在 Program.cs 中,將 PermissionAuthorizationHandler
添加到 DI 中:
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
builder.Services.AddAuthorization(options => { options.AddPolicy(UserPermission.UserCreate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserCreate))); options.AddPolicy(UserPermission.UserUpdate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserUpdate))); options.AddPolicy(UserPermission.UserDelete, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserDelete))); });
控制器如下:
[Route("api/[controller]/[action]")] [ApiController] public class UserController : ControllerBase { [HttpGet] [Authorize(UserPermission.UserCreate)] public ActionResult<string> UserCreate() => "UserCreate"; [HttpGet] [Authorize(UserPermission.UserUpdate)] public ActionResult<string> UserUpdate() => "UserUpdate"; [HttpGet] [Authorize(UserPermission.UserDelete)] public ActionResult<string> UserDelete() => "UserDelete"; }
基于上面的假定,用戶訪問接口的情況如下:
/api/User/UserCreate #成功 /api/User/UserUpdate #成功 /api/User/UserDelete #403無權(quán)限
至此,基于策略(Policy)的授權(quán)其實已經(jīng)基本完成。
接下去的內(nèi)容,將是對上面一些內(nèi)容的完善或補(bǔ)充。
完善:實現(xiàn)策略提供程序 PolicyProvider
一般添加授權(quán)策略如下是在 Program.cs 中,方式如下:
builder.Services.AddAuthorization(options => { options.AddPolicy("policy", policy => policy.RequireClaim("EmployeeNumber")); });
通過 AuthorizationOptions.AddPolicy
添加授權(quán)策略這種方式不靈活,無法動態(tài)添加。
通過實現(xiàn) IAuthorizationPolicyProvider
并添加到 DI 中,可以實現(xiàn)動態(tài)添加 Policy。
IAuthorizationPolicyProvider
的默認(rèn)實現(xiàn)為 DefaultAuthorizationPolicyProvider
。
實現(xiàn)一個 PolicyProvider 如下:
public class TestAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider, IAuthorizationPolicyProvider { public Test2AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) : base(options) {} public new Task<AuthorizationPolicy> GetDefaultPolicyAsync() => return base.GetDefaultPolicyAsync(); public new Task<AuthorizationPolicy?> GetFallbackPolicyAsync() return base.GetFallbackPolicyAsync(); public new Task<AuthorizationPolicy?> GetPolicyAsync(string policyName) { if (policyName.StartsWith(UserPermission.User)) { var policy = new AuthorizationPolicyBuilder("Bearer"); policy.AddRequirements(new PermissionAuthorizationRequirement(policyName)); return Task.FromResult<AuthorizationPolicy?>(policy.Build()); } return base.GetPolicyAsync(policyName); } }
注意:自定義的 TestAuthorizationPolicyProvider
必須實現(xiàn) IAuthorizationPolicyProvider
,否則添加到 DI 時會不生效。
在 Program.cs 中,將自定義的 PolicyProvider 添加到 DI 中:
builder.Services.AddSingleton<IAuthorizationPolicyProvider, TestAuthorizationPolicyProvider>();
注意:只會生效最后一個添加的 PolicyProvider。
補(bǔ)充:自定義 AuthorizationMiddleware
自定義 AuthorizationMiddleware 可以:
返回自定義的響應(yīng)
增強(qiáng)(或者說改變)默認(rèn)的 challenge 或 forbid 響應(yīng)
在 MiniApi 中幾乎都是形如 MapGet() 的分支節(jié)點,這類終結(jié)點無法使用 [Authorize] 標(biāo)簽,可以用使用 RequireAuthorization("Something") 進(jìn)行授權(quán),如:
app.MapGet("/helloworld", () => "Hello World!") .RequireAuthorization("AtLeast21");
“ASP.NET Core 6.0怎么添加JWT認(rèn)證和授權(quán)功能”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。