1.5 基於策略的授權 在上篇中,已經講到了授權訪問(authorization)的四種方式。其中Razor Pages授權約定和簡單授權二種方式更像是身份認證(authentication) ,因為只要是合法用戶登錄就能訪問資源。 而角色授權和聲明授權二種方式是真正的授權訪問(authorizat ...
1.5 基於策略的授權
在上篇中,已經講到了授權訪問(authorization)的四種方式。其中Razor Pages授權約定和簡單授權二種方式更像是身份認證(authentication) ,因為只要是合法用戶登錄就能訪問資源。 而角色授權和聲明授權二種方式是真正的授權訪問(authorization)。
下麵繼續講authorization的第五種方式--策略授權。策略授權由一個或多個需求(也可以稱"要求")組成(需求:TRequirement)。它在程式啟動時註冊為授權服務配置的一部分。在ConfigureServices方法中註冊。
(1) 註冊策略授權
創建了一個名為"AtLeast21"的策略授權,這個策略的需求是最小年齡需求,策略通過參數對象(IAuthorizationRequirement)提供,它要求最低年齡是21歲。
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddAuthorization(options => { options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber")); // MinimumAgeRequirement參數對象 實現了IAuthorizationRequirement options.AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21))); }); }
(2) 策略授權應用到mvc的控制器或Razor Pages
//用戶購買酒業務, 策略授權應用到控制器,要求用戶年齡不能低於21歲 [Authorize(Policy = "AtLeast21")] public class AlcoholPurchaseController : Controller { public IActionResult Index() => View(); }
//策略授權到razor pages的PageModel類 [Authorize(Policy = "AtLeast21")] public class AlcoholPurchaseModel : PageModel { }
在razor pages中,策略還可以應用到razor page授權約定中。
public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath, string policy);
(3) Requirement策略授權需求
策略授權需求實現IAuthorizationRequirement介面,用於策略需求對象參數傳遞。MinimumAgeRequirement就是一個需求參數對象。
using Microsoft.AspNetCore.Authorization; public class MinimumAgeRequirement : IAuthorizationRequirement { public int MinimumAge { get; } public MinimumAgeRequirement(int minimumAge) { MinimumAge = minimumAge; } }
(4) 策略授權處理程式類
授權處理程式負責評估要求的屬性(指策略授權邏輯處理,把當前用戶的年齡與策略要求年齡進行驗證)。 授權處理程式會針對提供的AuthorizationHandlerContext 來評估要求,確定是否允許訪問或拒絕。
實現策略授權處理程式,需要繼承AuthorizationHandler<TRequirement>,其中TRequirement就是參數對象。另外,一個處理程式也可以通過實現 IAuthorizationHandler 來處理多個類型的要求。
下麵是一對一關係的示例(一個Handler處理一個TRequirement對象),評估最低年齡要求:
public class MinimumAgeHandler: AuthorizationHandler<MinimumAgeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "LOCAL AUTHORITY")) { //TODO: Use the following if targeting a version of //.NET Framework older than 4.6: // return Task.FromResult(0); return Task.CompletedTask; } var dateOfBirth = Convert.ToDateTime( context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "LOCAL AUTHORITY").Value); int calculatedAge = DateTime.Today.Year - dateOfBirth.Year; if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge)) { calculatedAge--; } if (calculatedAge >= requirement.MinimumAge) { //滿足的要求作為其唯一參數 context.Succeed(requirement); } //TODO: Use the following if targeting a version of //.NET Framework older than 4.6: // return Task.FromResult(0); return Task.CompletedTask; } }
上面代碼是當前用戶主休是否有一個由已知的受信任頒發者(Issuer)頒發的出生日期聲明(ClaimTypes.DateOfBirth)。當前用戶缺少聲明時,無法進行授權,這種情況下會返回已完成的任務。如果存在聲明時,會計算用戶的年齡。 如果用戶滿足此要求所定義的最低年齡,則可以認為授權成功。 授權成功後,會調用 context.Succeed
,使用滿足的要求作為其唯一參數。
(5) 處理程式註入到服務集合,採用單例
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
在UserClaim用戶聲明表中,保存一條符合該策略授權的數據,當啟動程式,訪問AlcoholPurchase資源時,進入授權處理程式MinimumAgeHandler中, 執行context.Succeed(requirement)後, 授權成功。
1.5.1 多個需求使用一個處理程式
下麵是多個需求(TRequirement)使用一個處理程式,Handler實現IAuthorizationHandler介面,下麵示例是一個對多關係的許可權處理程式,可以在其中處理三種不同類型的需求:
public class PermissionHandler : IAuthorizationHandler { public Task HandleAsync(AuthorizationHandlerContext context) { //獲取策略中的多個需求,返回IEnumerable<IAuthorizationRequirement>類型 var pendingRequirements = context.PendingRequirements.ToList(); foreach (var requirement in pendingRequirements) { //讀取授權 if (requirement is ReadPermission) { if (IsOwner(context.User, context.Resource) || IsSponsor(context.User, context.Resource)) { context.Succeed(requirement); } } //編輯和刪除授權 else if (requirement is EditPermission || requirement is DeletePermission) { if (IsOwner(context.User, context.Resource)) { context.Succeed(requirement); } } } //TODO: Use the following if targeting a version of //.NET Framework older than 4.6: // return Task.FromResult(0); return Task.CompletedTask; }
具體詳細代碼,查看官方示例, Github。
1.5.2 處理程式應返回什麼? (有三種返回)
(1) 處理程式通過調用 context.Succeed(IAuthorizationRequirement requirement) 並傳遞已成功驗證的要求來表示成功。
(2) 處理程式通常不需要處理失敗(顯示加context.Fail()),因為同一要求的其他處理程式(1.5.3)可能會成功。
(3) 若要保證授權失敗,即使其它要求處理程式會成功,也會失敗,請調用context.Fail();
1.5.3 一個需求應用在多個處理程式
這裡授權處理正好與15.1相反,下麵這個示例是門禁卡授權策略需求, 你公司的門禁卡丟在家中,去公司後要求前臺給個臨時門禁卡來開門。這種情況下,只有一個需求,但有多個處理程式,每個處理程式針對單個要求進行檢查。
//策略授權需求,這裡沒有參數需求, 參數是硬編碼在處理程式中 public class BuildingEntryRequirement : IAuthorizationRequirement { }
//門禁卡處理程式 public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BuildingEntryRequirement requirement) { //需求參數硬編碼 BadgeId if (context.User.HasClaim(c => c.Type == "BadgeId" && c.Issuer == "http://microsoftsecurity")) { context.Succeed(requirement); } //TODO: Use the following if targeting a version of //.NET Framework older than 4.6: // return Task.FromResult(0); return Task.CompletedTask; } }
//臨時門禁卡處理程式 public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BuildingEntryRequirement requirement) { //需求參數硬編碼 TemporaryBadgeId if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" && c.Issuer == "https://microsoftsecurity")) { // We'd also check the expiration date on the sticker. context.Succeed(requirement); } //TODO: Use the following if targeting a version of //.NET Framework older than 4.6: // return Task.FromResult(0); return Task.CompletedTask; } }
// 註冊策略 services.AddAuthorization(options => { options.AddPolicy("BadgeEntry", policy =>policy.Requirements.Add(new BuildingEntryRequirement())); });
// 註入服務 services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>(); services.AddSingleton<IAuthorizationHandler, TemporaryStickerHandler>();
當[Authorize(Policy = " BadgeEntry ")]應用到控制器後,只要有一個處理程式成功,則策略授權成功。需要在UserClaim用戶聲明表中維護好ClaimType。
1.5.4 使用 func 滿足策略
有些情況下,策略很容易用代碼實現。 可以在通過 Func<AuthorizationHandlerContext, bool> 策略生成器配置策略時提供需要聲明 (RequireAssertion),例如上一個 BadgeEntryHandler 可以重寫,如下所示:
services.AddAuthorization(options => { options.AddPolicy("BadgeEntry", policy => policy.RequireAssertion(context => context.User.HasClaim(c => (c.Type == "BadgeId" || c.Type == "TemporaryBadgeId") && c.Issuer == "https://microsoftsecurity"))); });
總結:通過這二篇,熟悉了授權的五種方式,包括:
Razor Pages授權約定
簡單授權
角色授權
聲明授權
策略授權
其中聲明授權包含了角色授權,通過ClaimTypes.Role 可以在聲明中使用角色授權。
public const string Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
參考文獻