<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本文將分別介紹 Authentication(認證) 和 Authorization(授權)。
並以簡單的例子在 ASP.NET Core 6.0 的 WebAPI 中分別實現這兩個功能。
Authentication 和 Authorization 長得很像,傻傻分不清楚。
Authentication(認證):標識使用者的身份,一般發生在登入的時候。
Authorization(授權):授予使用者許可權,指定使用者能存取哪些資源;授權的前提是知道這個使用者是誰,所以授權必須在認證之後。
安裝 Microsoft.AspNetCore.Authentication.JwtBearer
在程式包管理器控制檯中:
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 6.0.1
在 appsetting.json 中,新增一個 Jwt 節點
"Jwt": { "SecretKey": "lisheng741@qq.com", "Issuer": "WebAppIssuer", "Audience": "WebAppAudience" }
在 Program.cs 檔案中註冊服務。
// 引入所需的名稱空間 using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; // …… var configuration = builder.Configuration; // 註冊服務 builder.Services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, //是否驗證Issuer ValidIssuer = configuration["Jwt:Issuer"], //發行人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), //過期時間容錯值,解決伺服器端時間不同步問題(秒) RequireExpirationTime = true, }; });
呼叫 UseAuthentication(認證),必須在所有需要身份認證的中介軟體前呼叫,比如 UseAuthorization(授權)。
// …… 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. 根據以上,生成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變為string var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); return token; }
該 JwtHelper 依賴於 IConfiguration(為了讀取組態檔),將 JwtHelper 的建立交由 DI 容器,在 Program.cs 中新增服務:
var configuration = builder.Configuration; builder.Services.AddSingleton(new JwtHelper(configuration));
將 JwtHelper 註冊為單例模式。
新建一個 AccountController,以建構函式方式注入 JwtHelper,新增兩個 Action:GetToken 用於獲取 Token,GetTest 打上 [Authorize] 標籤用於驗證認證。
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 等介面偵錯軟體偵錯
使用 Postman 呼叫 /api/Account/GetToken 生成 Token
在呼叫 /api/Account/GetTest 時傳入 Token,得到返回結果
方式二:在瀏覽器控制檯偵錯
偵錯 /api/Account/GetToken
var xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function() { if(this.readyState === 4) { console.log(token = this.responseText); //這裡用了一個全域性變數 token,為下一個介面服務 } }); xhr.open("GET", "/api/Account/GetToken"); xhr.send();
偵錯 /api/Account/GetTest
var xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function() { if(this.readyState === 4) { console.log(this.status, this.responseText); //this.status為響應狀態碼,401為無認證狀態 } }); xhr.open("GET", "/api/Account/GetTest"); xhr.setRequestHeader("Authorization",`Bearer ${token}`); //附帶上 token xhr.send();
注意:授權必須基於認證,即:若沒有完成上文關於認證的設定,則下面的授權是不會成功的。
授權部分,將先介紹相關標籤、授權方式,再介紹基於策略的授權。這三部分大致的內容如下描述:
相關標籤:Authorize 和 AllowAnonymous
授權方式:介紹 Policy、Role、Scheme 的基本內容
基於策略(Policy)的授權:深入 Policy 授權方式
授權相關標籤具體請查考官方檔案簡單授權
[Authorize]
打上該標籤的 Controller 或 Action 必須經過認證,且可以標識需要滿足哪些授權規則。
授權規則可以是 Policy(策略)、Roles(角色) 或 AuthenticationSchemes(方案)。
[Authorize(Policy = "", Roles ="", AuthenticationSchemes ="")]
[AllowAnonymous]
允許匿名存取,級別高於 [Authorize] ,若兩者同時作用,將生效 [AllowAnonymous]
基本上授權只有:Policy、Role、Scheme 這3種方式,對應 Authorize 標籤的3個屬性。
推薦的授權方式,在 ASP.NET Core 的官方檔案提及最多的。一個 Policy 可以包含多個要求(要求可能是 Role 匹配,也可能是 Claims 匹配,也可能是其他方式。)
下面舉個基礎例子(說是基礎例子,主要是基於 Policy 的授權方式可以不斷深入追加一些設定):
在 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] 標籤即可生效:
[Authorize(Policy = "policy1")] public class TestController : ControllerBase
或在控制器的 Action 上:
public class TestController : ControllerBase { [Authorize(Policy = "policy1")] public ActionResult<string> GetTest => "GetTest"; }
基於角色授權,只要使用者擁有角色,即可通過授權驗證。
在認證時,給使用者新增角色相關的 Claim ,即可標識使用者擁有的角色(注:一個使用者可以擁有多個角色的 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,當然也可以是自定義的方案。
由於這種方式不常用,這裡不做展開,請參考官方檔案按方案限制標識。
基於策略(Policy)的授權
上面已經提及了一個基於策略授權的基礎例子,下面將繼續深入這種授權方式。
1 授權過程
在不斷深入 Policy 這種方式的授權之前,有必要將授權的過程描述一下。授權過程描述建議結合原始碼檢視,這樣能更清楚其中的作用。當然,這一部分是比較難懂,筆者表述可能也不夠清晰,而這一部分對於完成授權的設定也不會有影響,故而如果讀者看不明白或無法理解,可以暫且跳過,不必糾結。
建議看一下ASP.NET Core使用JWT認證授權的方法這篇文章,文章將授權相關的原始碼整理出來了,並說明了其中的關係。
這裡簡單梳理一下:
與授權相關的 interface 和 class 如下:
IAuthorizationService #驗證授權的服務,主要方法AuthorizeAsync DefaultAuthorizationService #IAuthorizationService的預設實現 IAuthorizationHandler #負責檢查是否滿足要求,主要方法HandleAsync IAuthorizationRequirement #只有屬性,沒有方法;用於標記服務,以及用於追蹤授權是否成功的機制。 AuthorizationHandler<TRequirement> #主要方法HandleRequirementAsync
這些 interface 和 class 的關係以及授權過程是這樣的:
DefaultAuthorizationService
實現 IAuthorizationService
的 AuthorizeAsync
方法。
AuthorizeAsync
方法會獲取到所有實現了 IAuthorizationHandler
的範例,並回圈呼叫所有範例的 HandleAsync 方法檢查是否滿足授權要求,如果有任一一個 HandleAsync
返回了 Fail 則將結束迴圈(細節請參考官方檔案處理程式返回結果),並禁止使用者存取。
IAuthorizationHandler
的作用如上一點所述,提供了一個 HandleAsync
方法,用於檢查授權。
IAuthorizationRequirement
是一個要求,主要是配合 AuthorizationHandler<TRequirement>
使用。
AuthorizationHandler<TRequirement>
實現了 IAuthorizationHandler
的 HandleAsync
方法,並提供了一個 HandleRequirementAsync
的方法。HandleRequirementAsync
用於檢查 Requirement
(要求)是否滿足。HandleAsync
的預設實現為獲取所有實現 TRequirement
的請求(且該請求由 Policy 新增進列表裡),迴圈呼叫 HandleRequirementAsync
,檢查哪個要求(Requirement)能滿足授權。
簡述一下:
[Authorize]
標籤生效時,呼叫的是 IAuthorizationService
的 AuthorizeAsync
(由 DefaultAuthorizationService
實現)。
AuthorizeAsync
會去呼叫所有 IAuthorizationHandler
的 HandleAsync
(由 AuthorizationHandler<TRequirement>
實現)。
HandleAsync
會去呼叫 AuthorizationHandler<TRequirement>
的HandleRequirementAsync
的方法。
注意:這裡只是列出了主要的介面和類,部分沒有列出,如:IAuthorizationHandlerProvider
(這個介面的預設實現 DefaultAuthorizationHandlerProvider
,主要是用於收集 IAuthorizationHandler
並返回 IEnumerable<IAuthorizationHandler>
)
2 實現說明
IAuthorizationService
已預設實現,不需要我們做額外工作。
IAuthorizationHandler
由 AuthorizationHandler<TRequirement>
實現。
所以我們要做的,是:
第一步,準備 Requirement 實現 IAuthorizationRequirement
第二步,新增一個 Handler 程式繼承 AuthorizationHandler<TRequirement>
並重寫 HandleRequirementAsync
方法
關於具體實現,可以參考asp.net core 授權詳解基於許可權的授權部分,該文章思路已十分清晰,這裡將其主要步驟列出來。
在實現 Requirement 之前,我們需要先定義一些許可權項,主要用於後續作為 Policy 的名稱,並傳入 我們實現的 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"; }
如上,定義了“增”、“刪”、“改”等許可權,其中 User 將擁有完整許可權。
public class PermissionAuthorizationRequirement : IAuthorizationRequirement { public PermissionAuthorizationRequirement(string name) { Name = name; } public string Name { get; set; } }
使用 Name 屬性表示許可權的名稱,與 UserPermission 中的常數對應。
這裡假定使用者的 Claim 中 ClaimType 為 Permission 的項,如:
new Claim("Permission", UserPermission.UserCreate), new Claim("Permission", UserPermission.UserUpdate)
如上,標識該使用者使用者 UserCreate 和 UserUpdate 的許可權。
注意:當然,實際程式我們肯定不是這樣實現的,這裡只是簡易範例。
接著,實現一個授權 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 是否滿足授權,滿足則執行 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無許可權
至此,基於策略(Policy)的授權其實已經基本完成。
接下去的內容,將是對上面一些內容的完善或補充。
完善:實現策略提供程式 PolicyProvider
一般新增授權策略如下是在 Program.cs 中,方式如下:
builder.Services.AddAuthorization(options => { options.AddPolicy("policy", policy => policy.RequireClaim("EmployeeNumber")); });
通過 AuthorizationOptions.AddPolicy
新增授權策略這種方式不靈活,無法動態新增。
通過實現 IAuthorizationPolicyProvider
並新增到 DI 中,可以實現動態新增 Policy。
IAuthorizationPolicyProvider
的預設實現為 DefaultAuthorizationPolicyProvider
。
實現一個 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
必須實現 IAuthorizationPolicyProvider
,否則新增到 DI 時會不生效。
在 Program.cs 中,將自定義的 PolicyProvider 新增到 DI 中:
builder.Services.AddSingleton<IAuthorizationPolicyProvider, TestAuthorizationPolicyProvider>();
注意:只會生效最後一個新增的 PolicyProvider。
補充:自定義 AuthorizationMiddleware
自定義 AuthorizationMiddleware 可以:
具體請檢視官方檔案自定義 AuthorizationMiddleware 的行為
在 MiniApi 中幾乎都是形如 MapGet() 的分支節點,這類終結點無法使用 [Authorize] 標籤,可以用使用 RequireAuthorization("Something") 進行授權,如:
app.MapGet("/helloworld", () => "Hello World!") .RequireAuthorization("AtLeast21");
ASP.NET Core 6.0 官方檔案:授權策略提供程式
ASP.NET Core 認證與授權6:授權策略是怎麼執行的?(mark:這篇很強!)
到此這篇關於ASP.NET Core 6.0 新增 JWT 認證和授權的文章就介紹到這了,更多相關ASP.NET Core 新增 JWT 認證和授權內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45