ASP.NET Core 认证与授权[5]:初识授权
經過前面幾章的姍姍學步,我們了解了在 ASP.NET Core 中是如何認證的,終于來到了授權階段。在認證階段我們通過用戶令牌獲取到用戶的Claims,而授權便是對這些的Claims的驗證,如:是否擁有Admin的角色,姓名是否叫XXX等等。本章就來介紹一下 ASP.NET Core 的授權系統的簡單使用。
簡單授權
在ASP.NET 4.x中,我們通常使用Authorize過濾器來進行授權,它可以作用在Controller和Action上面,也可以添加到全局過濾器中。而在ASP.NET Core中也有一個Authorize特性(但不是過濾器),用法類似:
[Authorize] // Controller級別public class SampleDataController : Controller{[Authorize] // Action級別public IActionResult SampleAction() ? ?{} }
IAllowAnonymous
在ASP.NET 4.x中,我們最常用的另一個特性便是AllowAnonymous,用來設置某個Controller或者Action跳過授權,它在 ASP.NET Core 中同樣適用:
[Authorize]public class AccountController : Controller{[AllowAnonymous] ? ?public ActionResult Login() ? ?{} ? ?public ActionResult Logout() ? ?{} }
如上,LoginAction便不再需要授權,同樣,在 ASP.NET Core 中提供了一個統一的IAllowAnonymous接口,在授權邏輯中都是通過該接口來判斷是否跳過授權驗證的。
public interface IAllowAnonymous{ }[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]public class AllowAnonymousAttribute : Attribute, IAllowAnonymous{ }IAuthorizeData
上面提到,在 ASP.NET Core 中,AuthorizeAttribute不再是一個MVC中的Filter了,而只是一個簡單的實現了IAuthorizeData接口的Attribute:
public interface IAuthorizeData{ ??string Policy { get; set; } ? ?
?Roles { get; set; } ?
?string AuthenticationSchemes { get; set; } }[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]public class AuthorizeAttribute : Attribute, IAuthorizeData{ ? ?public AuthorizeAttribute() { } ? ?public AuthorizeAttribute(string policy) ? ?{Policy = policy;} ?
? ?public string Policy { get; set; } ? ?
? ?public string Roles { get; set; } ?
? ?public string AuthenticationSchemes { get; set; } }
記得第一次在ASP.NET Core中實現自定義授權時,按照以前的經驗,直接繼承自AuthorizeAttribute,然后準備重寫OnAuthorization方法,結果懵逼了。然后在MVC的源碼中,苦苦搜尋AuthorizeAttribute的蹤跡,卻毫無所獲,后來才注意到它實現了IAuthorizeData接口,該接口才是認證的源頭,而Authorize特性只是認證信息的載體,并不包含任何邏輯。IAuthorizeData中定義的Policy, Roles, AuthenticationSchemes三個屬性分別代表著 ASP.NET Core 授權系統中的三種授權方式。
基于角色的授權
基于角色的授權,我們都比較熟悉,使用方式如下:
[Authorize(Roles = "Admin")] // 多個Role可以使用,分割public class SampleDataController : Controller{... }
基于角色的授權的邏輯與ASP.NET 4.x類似,都是使用我在《初識認證》中介紹的IsInRole方法來實現的。
基于Scheme的授權
對于AuthenticationScheme我在前面幾章也都介紹過,比如Cookie認證默認使用的AuthenticationScheme就是Cookies,在JwtBearer認證中,默認的Scheme就是Bearer。
當初在學習認證時,還在疑惑,如何在使用Cookie認證的同時又支持Bearer認證呢?因為在認證中只能設置一個Scheme來執行,當看到這里豁然開朗,后面會詳細介紹。
[Authorize(AuthenticationSchemes = "Cookies")] // 多個Scheme可以使用,分割public class SampleDataController : Controller{... }
當我們的應用程序中,同時使用了多種認證Scheme時,AuthenticationScheme授權就非常有用,在該授權模式下,會通過context.AuthenticateAsync(scheme)重新獲取Claims。
基于策略的授權
在ASP.NET Core中,重新設計了一種更加靈活的授權方式:基于策略的授權,也是授權的核心。
在使用基于策略的授權時,首先要定義授權策略,而授權策略本質上就是對Claims的一系列斷言。
public void ConfigureServices(IServiceCollection services){services.AddMvc();services.AddAuthorization(options =>{options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));}); }如上,我們定義了一個名稱為EmployeeOnly的授權策略,它要求用戶的Claims中必須包含類型為EmployeeNumber的Claim。
其實,基于角色的授權和基于Scheme的授權,只是一種語法上的便捷,最終都會生成授權策略,后文會詳解介紹。
然后便可以在Authorize特性中通過Policy屬性來指定授權策略:
[Authorize(Policy = "EmployeeOnly")]public class SampleDataController : Controller{}授權策略詳解
AddAuthorization
授權策略的定義使用了AddAuthorization擴展方法,我們來看看它的源碼:
public static class AuthorizationServiceCollectionExtensions{ ??public static IServiceCollection AddAuthorization(this IServiceCollection services) ? ?{ ? ? ? ?services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>());services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>());services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>());services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>());services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>());services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>()); ? ? ? ?return services;} ? ?public static IServiceCollection AddAuthorization(this IServiceCollection services, Action<AuthorizationOptions> configure) ? ?{services.Configure(configure); ? ? ?
? ?return services.AddAuthorization();} }
首先,是對授權進行配置的AuthorizationOptions,然后在DI系統中注冊了幾個核心對象的默認實現,我們一一來看。
AuthorizationOptions
對于Options模式,大家應該都比較熟悉了,AuthorizationOptions是添加和獲取授權策略的入口點:
public class AuthorizationOptions{ ? ?private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase); ? ?// 在上一個策略驗證失敗后,是否繼續執行下一個授權策略public bool InvokeHandlersAfterFailure { get; set; } = true;
? ?public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); ? ?public void AddPolicy(string name, AuthorizationPolicy policy) ? ?{PolicyMap[name] = policy;} ? ?
? ?
? ?public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy) ? ?{ ? ? ? ?var policyBuilder = new AuthorizationPolicyBuilder();configurePolicy(policyBuilder);AddPolicy(name,policyBuilder.Build());} ? ?public AuthorizationPolicy GetPolicy(string name) ? ?{ ? ?
? ?? ?return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;} }
首先是一個PolicyMap字典,我們定義的策略都保存在其中,AddPolicy方法只是簡單的將策略添加到該字典中,而其DefaultPolicy屬性表示默認策略,初始值為:“已認證用戶”。
在AuthorizationOptions中主要涉及到AuthorizationPolicy和AuthorizationPolicyBuilder兩個對象。
AuthorizationPolicy
在 ASP.NET Core 中,授權策略具體表現為一個AuthorizationPolicy對象:
public class AuthorizationPolicy{ ??public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes) {} ? ?public IReadOnlyList<IAuthorizationRequirement> Requirements { get; } ? ?public IReadOnlyList<string> AuthenticationSchemes { get; } ? ?public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies) { ? ? ? ?return Combine((IEnumerable<AuthorizationPolicy>)policies);} ?
?
??public static AuthorizationPolicy Combine(IEnumerable<AuthorizationPolicy> policies) { ? ? ? ?foreach (var policy in policies){builder.Combine(policy);} ? ? ? ?return builder.Build();} ?
??
???public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) { ? ? ? ?foreach (var authorizeDatum in authorizeData){any = true; ? ? ? ? ? ?var useDefaultPolicy = true; ? ? ? ? ? ?if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy)){policyBuilder.Combine(await policyProvider.GetPolicyAsync(authorizeDatum.Policy));useDefaultPolicy = false;} ? ? ? ? ? ?var rolesSplit = authorizeDatum.Roles?.Split(','); ? ? ? ? ? ?if (rolesSplit != null && rolesSplit.Any()){policyBuilder.RequireRole(rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()));useDefaultPolicy = false;} ? ? ? ? ? ?var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(','); ? ? ? ? ? ?if (authTypesSplit != null && authTypesSplit.Any()){ ? ? ? ? ? ? ? ?foreach (var authType in authTypesSplit){ ? ? ? ? ? ? ? ? ? ?if (!string.IsNullOrWhiteSpace(authType)){policyBuilder.AuthenticationSchemes.Add(authType.Trim());}}} ? ? ?
???? ? ?if (useDefaultPolicy){policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());}} ? ? ?
???? ?return any ? policyBuilder.Build() : null;} }
如上,Combine方法通過調用AuthorizationPolicyBuilder來完成授權策略的合并,而CombineAsync則是將我們上面介紹的IAuthorizeData轉換為授權策略,因此上面說基于角色/Scheme的授權本質上都是基于策略的授權。
對于AuthenticationSchemes屬性,我們在前幾章介紹認證時經常看到,用來表示我們使用哪個認證Scheme來獲取用戶的Claims,如果指定多個,則會合并它們的Claims,其實現下一章再來介紹。
而Requirements屬性則是策略的核心了,每一個Requirement都代表一個授權條件,我們就先來了解一下它。
IAuthorizationRequirement
Requirement使用IAuthorizationRequirement接口來表示:
public interface IAuthorizationRequirement{ }IAuthorizationRequirement接口中并沒有任何成員,在 ASP.NET Core 中內置了一些常用的實現:
-
AssertionRequirement :使用最原始的斷言形式來聲明授權策略。
-
DenyAnonymousAuthorizationRequirement :用于表示禁止匿名用戶訪問的授權策略,并在AuthorizationOptions中將其設置為默認策略。
-
ClaimsAuthorizationRequirement :用于表示判斷Cliams中是否包含預期的Claims的授權策略。
-
RolesAuthorizationRequirement :用于表示使用ClaimsPrincipal.IsInRole來判斷是否包含預期的Role的授權策略。
-
NameAuthorizationRequirement:用于表示使用ClaimsPrincipal.Identities.Name來判斷是否包含預期的Name的授權策略。
-
OperationAuthorizationRequirement:用于表示基于操作的授權策略。
其邏輯也都非常簡單,我就不再一一介紹,只展示一下RolesAuthorizationRequirement的代碼片段:
public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement{ ? ?public IEnumerable<string> AllowedRoles { get; } ? ?protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement) ? ?{... ? ? ? ?if (requirement.AllowedRoles.Any(r => context.User.IsInRole(r))){context.Succeed(requirement);} ? ? ? ?return Task.CompletedTask;} }其AllowedRoles表示允許授權通過的角色,而它還實現了IAuthorizationHandler接口,用來完成授權的邏輯。
public interface IAuthorizationHandler{ ??Task HandleAsync(AuthorizationHandlerContext context); }
AuthorizationRequirement并不是一定要實現IAuthorizationHandler接口,后文會詳細介紹。
AuthorizationPolicyBuilder
在上面已經多次用到AuthorizationPolicyBuilder,它提供了一系列創建AuthorizationPolicy的快捷方法:
public class AuthorizationPolicyBuilder{ ??public AuthorizationPolicyBuilder(params string[] authenticationSchemes); ?
?
? ?public AuthorizationPolicyBuilder(AuthorizationPolicy policy); ?
? ?
? ? ?public IList<IAuthorizationRequirement> Requirements { get; set; }
?? ?public IList<string> AuthenticationSchemes { get; set; } ?
?? ?public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes); ? ?public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements); ? ?public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, bool> handler); ? ?public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, Task<bool>> handler) ? ?{Requirements.Add(new AssertionRequirement(handler)); ? ? ?
?? ? ?return this;} ?
? ?public AuthorizationPolicyBuilder RequireAuthenticatedUser() ? ?{Requirements.Add(new DenyAnonymousAuthorizationRequirement()); ?
?? ? ? ? ? ?return this;} ?
? ?public AuthorizationPolicyBuilder RequireClaim(string claimType); ?
?? ??public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] requiredValues); ? ?public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> requiredValues) ? ?{Requirements.Add(new ClaimsAuthorizationRequirement(claimType, requiredValues)); ? ? ? ?return this;} ?
?
? ?public AuthorizationPolicyBuilder RequireRole(params string[] roles);
?? ??? ?public AuthorizationPolicyBuilder RequireRole(IEnumerable<string> roles) ? ?{Requirements.Add(new RolesAuthorizationRequirement(roles)); ? ? ? ?return this;} ?
?public AuthorizationPolicyBuilder RequireUserName(string userName) ? ?{Requirements.Add(new NameAuthorizationRequirement(userName)); ? ?
?? ?return this;} ? ?public AuthorizationPolicy Build(); ?
?? ? ?public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy); }
在上面介紹的幾個Requirement,除了OperationAuthorizationRequirement外,都有對應的快捷添加方法,由于OperationAuthorizationRequirement并不屬于基于資源的授權,所以不在這里,其用法留在其后續章節再來介紹。
整個授權策略的內容也就這么多,并不復雜,整個結構大致如下:
基于策略的授權進階
在上一小節,我們探索了一下授權策略的源碼,現在就來實戰一下。
我們使用AuthorizationPolicyBuilder可以很容易的在策略定義中組合我們需要的Requirement:
public void ConfigureServices(IServiceCollection services){ ??var commonPolicy = new AuthorizationPolicyBuilder().RequireClaim("MyType").Build();services.AddAuthorization(options =>{options.AddPolicy("User", policy => policy.RequireAssertion(context => context.User.HasClaim(c => (c.Type == "EmployeeNumber" || c.Type == "Role"))));options.AddPolicy("Employee", policy => policy.RequireRole("Admin").RequireUserName("Alice").RequireClaim("EmployeeNumber").Combine(commonPolicy));}); }
如上,如果需要,我們還可以定義一個公共的策略對象,然后在策略定義中直接將其合并進來。
自定義策略
當內置的Requirement不能滿足我們的需求時,我們也可以很容易的定義自己的Requirement:
public class MinimumAgeRequirement : AuthorizationHandler<NameAuthorizationRequirement>, IAuthorizationRequirement{ ? ?public MinimumAgeRequirement(int minimumAge) ? ?{MinimumAge = minimumAge;} ??public int MinimumAge { get; private set; } ?
?protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement) ? ?{ ? ? ? ?if (context.User != null && context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth){ ? ? ? ? ? ?var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value); ? ? ? ? ? ?int calculatedAge = DateTime.Today.Year - dateOfBirth.Year; ? ? ? ? ? ?if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge)){calculatedAge--;} ? ? ? ? ? ?if (calculatedAge >= requirement.MinimumAge){context.Succeed(requirement);}} ? ? ?
? ?return Task.CompletedTask;} }
然后就可以直接在AddPolicy中使用了:
services.AddAuthorization(options => {options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21))); });我們自定義的 Requirement 若想得到 ASP.NET Core 授權系統的執行,除了上面示例中的實現IAuthorizationHandler接口外,也可以單獨定義AuthorizationHandler,這樣可以更好的使用DI系統,并且還可以定義多個Handler,下面就來演示一下。
多Handler模式
授權策略中的多個Requirement,它們屬于 & 的關系,只用全部驗證通過,才能最終授權成功。但是在有些場景下,我們可能希望一個授權策略可以適用多種情況,比如,我們進入公司時需要出示員工卡才可以被授權進入,但是如果我們忘了帶員工卡,可以去申請一個臨時卡,同樣可以授權成功:
public class EnterBuildingRequirement : IAuthorizationRequirement{ }public class BadgeEntryHandler : AuthorizationHandler<EnterBuildingRequirement> { ?
?protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EnterBuildingRequirement requirement) ? ?{ ? ? ? ?if (context.User.HasClaim(c => c.Type == ClaimTypes.BadgeId){context.Succeed(requirement);} ? ? ? ?else{ ? ? ? ? ? ?// context.Fail();} ? ? ? ?return Task.CompletedTask;} }
public class HasTemporaryStickerHandler : AuthorizationHandler<EnterBuildingRequirement> { ? ?protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EnterBuildingRequirement requirement) ? ?{ ? ? ? ?if (context.User.HasClaim(c => c.Type == ClaimTypes.TemporaryBadgeId){context.Succeed(requirement);} ? ? ? ?return Task.CompletedTask;} }
如上,我們定義了兩個Handler,但是想讓它們得到執行,還需要將其注冊到DI系統中:
services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>(); services.AddSingleton<IAuthorizationHandler, HasTemporaryStickerHandler>();此時,在我們的應該程序中使用EnterBuildingRequirement的授權時,將會依次執行這兩個Handler。而在上面介紹AuthorizationOptions時,提到它還有一個InvokeHandlersAfterFailure屬性,在這里就派上用場了,只有其為true時(默認為True),才會在當前 AuthorizationHandler 授權失敗時,繼續執行下一個 AuthorizationHandler。
在上面的示例中,我們使用context.Succeed(requirement)將授權結果設置為成功,而失敗時并沒有做任何標記,正常情況下都是這樣做的。但是如果需要,我們可以通過調用context.Fail()方法顯式的將授權結果設置為失敗,那么,不管其他 AuthorizationHandler 是成功還是失敗,最終結果都將是授權失敗。
總結
ASP.NET Core 授權策略是一種非常強大、靈活的權限驗證方案,能夠滿足大部分的授權場景。通過本文對授權策略的詳細介紹,我想應該能夠靈活的使用基于策略的授權了,但是授權策略到底是怎么執行的呢?在下一章中,就來完整的探索一下 ASP.NET Core 授權系統的執行流程。
相關文章:
-
ASP.NET Core 認證與授權[4]:JwtBearer認證
-
ASP.NET Core 認證與授權[2]:Cookie認證
-
ASP.NET Core 認證與授權[3]:OAuth & OpenID Connect認證
-
Asp.Net Core 2.0 多角色權限認證
-
asp.net core 2.0 web api基于JWT自定義策略授權
原文:http://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.html
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結
以上是生活随笔為你收集整理的ASP.NET Core 认证与授权[5]:初识授权的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过 Visual Studio 的“代
- 下一篇: ASP.NET Core缓存静态资源