一、前言
回顧:認證授權方案之授權初識
從上一節中,我們在對授權系統已經有了初步的認識和使用,可以發現,asp.net core為我們提供的授權策略是一個非常強大豐富且靈活的認證授權方案,能夠滿足大部分的授權場景。
在ConfigureServices中配置服務:將授權服務添加到容器
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("customizePermisson",
policy => policy
.Requirements
.Add(new PermissionRequirement("user")));
});
//此外,還需要在 IAuthorizationHandler 類型的范圍內向 DI 系統注冊新的處理程序:
services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
}
在Configure中注冊管道:運行使用調用方法來配置Http請求管道
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//開啟授權
app.UseAuthorization();
}
通過以上幾行代碼的實現,就可以進行授權了,這個時候,你可以會問,這幾行代碼都進行了什么操作實現授權的?
好了,繼續回到上節最后說的在這一節中對授權策略的核心進行一步步的揭秘的。
二、開始
引入整體結構
2.1 添加授權AddAuthorization
添加授權策略服務使用AddAuthorization
方法,以便調用。
從源碼可以發現,從core3.0后,由之前在core2.0中的AuthorizationServiceCollectionExtensions.cs
文件中,原來的AddAuthorization
的方法變為了AddAuthorizationCore
方法,微軟在這一塊進行了封裝在PolicyServiceCollectionExtensions.cs
文件中,沿用了之前AddAuthorization
拓展名稱,不影響之前版本的使用。
我們來看看aspnetcore源碼:
public static class PolicyServiceCollectionExtensions
{
public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAddSingleton<AuthorizationPolicyMarkerService>();
services.TryAddTransient<IPolicyEvaluator, PolicyEvaluator>();
services.TryAddTransient<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();
return services;
}
public static IServiceCollection AddAuthorization(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddAuthorizationCore();
services.AddAuthorizationPolicyEvaluator();
return services;
}
public static IServiceCollection AddAuthorization(this IServiceCollection services, Action<AuthorizationOptions> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddAuthorizationCore(configure);
services.AddAuthorizationPolicyEvaluator();
return services;
}
}
public static class AuthorizationServiceCollectionExtensions
{
public static IServiceCollection AddAuthorizationCore(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(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 AddAuthorizationCore(this IServiceCollection services, Action<AuthorizationOptions> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.Configure(configure);
return services.AddAuthorizationCore();
}
}
由上可知,在調用AddAuthorization
方法進行授權配置的時候,需要使用到AuthorizationOptions
委托方式傳參。
所以我們再來看看下面這一行代碼,通過AddPolicy
實現添加策略方式。
options.AddPolicy("customizePermisson",policy => policy.Requirements.Add(new PermissionRequirement("user")));
查看源碼發現是引用了AuthorizationOptions
對象。
2.2 配置選項AuthorizationOptions
授權選項實現添加和授權配置,提供授權服務的配置。
源碼如下:
public class AuthorizationOptions
{
private Dictionary<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 AuthorizationPolicy? FallbackPolicy { get; set; }
public void AddPolicy(string name, AuthorizationPolicy policy)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
PolicyMap[name] = policy;
}
public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (configurePolicy == null)
{
throw new ArgumentNullException(nameof(configurePolicy));
}
var policyBuilder = new AuthorizationPolicyBuilder();
configurePolicy(policyBuilder);
PolicyMap[name] = policyBuilder.Build();
}
public AuthorizationPolicy GetPolicy(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (PolicyMap.TryGetValue(name, out var value))
{
return value;
}
return null;
}
}
定義一個字典
private Dictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
目的在於將定義的授權策略方式都保存在這個聲明的PolicyMap
當中,而其中AddPolicy
方法是將配置的策略添加到字典中。
public void AddPolicy(string name, AuthorizationPolicy policy);
public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy);
而這方法中涉及到兩種不同的傳參對象AuthorizationPolicy
和AuthorizationPolicyBuilder
。
2.3 授權策略 AuthorizationPolicy
表示授權要求和方案的集合。具體源碼如下:
public class AuthorizationPolicy
{
public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes)
{
if (requirements == null)
{
throw new ArgumentNullException(nameof(requirements));
}
if (authenticationSchemes == null)
{
throw new ArgumentNullException(nameof(authenticationSchemes));
}
if (requirements.Count() == 0)
{
throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty);
}
Requirements = new List<IAuthorizationRequirement>(requirements).AsReadOnly();
AuthenticationSchemes = new List<string>(authenticationSchemes).AsReadOnly();
}
public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
public IReadOnlyList<string> AuthenticationSchemes { get; }
public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies)
{
if (policies == null)
{
throw new ArgumentNullException(nameof(policies));
}
return Combine((IEnumerable<AuthorizationPolicy>)policies);
}
public static AuthorizationPolicy Combine(IEnumerable<AuthorizationPolicy> policies)
{
if (policies == null)
{
throw new ArgumentNullException(nameof(policies));
}
var builder = new AuthorizationPolicyBuilder();
foreach (var policy in policies)
{
builder.Combine(policy);
}
return builder.Build();
}
public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
{
if (policyProvider == null)
{
throw new ArgumentNullException(nameof(policyProvider));
}
if (authorizeData == null)
{
throw new ArgumentNullException(nameof(authorizeData));
}
// Avoid allocating enumerator if the data is known to be empty
var skipEnumeratingData = false;
if (authorizeData is IList<IAuthorizeData> dataList)
{
skipEnumeratingData = dataList.Count == 0;
}
AuthorizationPolicyBuilder policyBuilder = null;
if (!skipEnumeratingData)
{
foreach (var authorizeDatum in authorizeData)
{
if (policyBuilder == null)
{
policyBuilder = new AuthorizationPolicyBuilder();
}
var useDefaultPolicy = true;
if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
{
var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
if (policy == null)
{
throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
}
policyBuilder.Combine(policy);
useDefaultPolicy = false;
}
var rolesSplit = authorizeDatum.Roles?.Split(',');
if (rolesSplit != null && rolesSplit.Any())
{
var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
policyBuilder.RequireRole(trimmedRolesSplit);
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());
}
}
}
// If we have no policy by now, use the fallback policy if we have one
if (policyBuilder == null)
{
var fallbackPolicy = await policyProvider.GetFallbackPolicyAsync();
if (fallbackPolicy != null)
{
return fallbackPolicy;
}
}
return policyBuilder?.Build();
}
}
我們從源碼中可以發現,Authorization
對象 Combine
方法目的在於將授權策略進行合並,同時調用了AuthorizationPolicyBuilder
對象中Combine
方法,對授權方案或者授權策略進行合並。再來看看AuthorizationPolicy
對象中的CombineAsync
方法,這里的參數用到了IAuthorizeData
,同時這個方法的過程是將可能基於角色,基於方案或者基於策略都合並轉換為是授權策略的方式,也是通過調用AuthorizationPolicyBuilder
對象來實現合並。 所以可以看得出AuthorizationPolicyBuilder
提供了一些創建AuthorizationPolicy
的方法。
這個時候,我們可以發現,其實之前說的基於角色、基於方案的授權方式本質上來說都是基於策略授權。
2.4 構建策略AuthorizationPolicyBuilder
除了上面說到使用AuthorizationPolicy
對象之外,我們還可以用AuthorizationPolicyBuilder
對象以Buider
來創建AuthorizationPolicy
對象,將多個AuthorizationPolicy
對象提供的數組進行合並,所以AuthorizationPolicyBuilder
提供的Combine
方法的使用,為AuthorizationPolicy
授權構建提供了許多便捷的方式。
public class AuthorizationPolicyBuilder
{
public AuthorizationPolicyBuilder(params string[] authenticationSchemes)
{
AddAuthenticationSchemes(authenticationSchemes);
}
public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
{
Combine(policy);
}
public IList<IAuthorizationRequirement> Requirements { get; set; } = new List<IAuthorizationRequirement>();
public IList<string> AuthenticationSchemes { get; set; } = new List<string>();
public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes)
{
foreach (var authType in schemes)
{
AuthenticationSchemes.Add(authType);
}
return this;
}
public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements)
{
foreach (var req in requirements)
{
Requirements.Add(req);
}
return this;
}
public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
{
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
AddAuthenticationSchemes(policy.AuthenticationSchemes.ToArray());
AddRequirements(policy.Requirements.ToArray());
return this;
}
public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
return RequireClaim(claimType, (IEnumerable<string>)allowedValues);
}
public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> allowedValues)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues));
return this;
}
public AuthorizationPolicyBuilder RequireClaim(string claimType)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues: null));
return this;
}
public AuthorizationPolicyBuilder RequireRole(params string[] roles)
{
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
return RequireRole((IEnumerable<string>)roles);
}
public AuthorizationPolicyBuilder RequireRole(IEnumerable<string> roles)
{
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
Requirements.Add(new RolesAuthorizationRequirement(roles));
return this;
}
public AuthorizationPolicyBuilder RequireUserName(string userName)
{
if (userName == null)
{
throw new ArgumentNullException(nameof(userName));
}
Requirements.Add(new NameAuthorizationRequirement(userName));
return this;
}
public AuthorizationPolicyBuilder RequireAuthenticatedUser()
{
Requirements.Add(new DenyAnonymousAuthorizationRequirement());
return this;
}
public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, bool> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
Requirements.Add(new AssertionRequirement(handler));
return this;
}
public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, Task<bool>> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
Requirements.Add(new AssertionRequirement(handler));
return this;
}
public AuthorizationPolicy Build()
{
return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct());
}
}
由上面多出出現的IAuthorizationRequirement
對象可以發現,授權要求Requirement
屬性是策略的核心方案,每一種Requirement
都代表一種授權方式。同時IAuthorizationPolicyBuilder
為這些預定義的方案創建了它們對應的使用方式並將其添加到Requirements
集合中。
2.5 授權要求IAuthorizationRequirement
public interface IAuthorizationRequirement
{
}
接口並沒有任何實現成員,因為授權要求是具有不同的表現形式的,所有才沒有具體的實現成員。授權要求目的在於檢驗某個當前用戶是否具有相應的要求, 所以大部分IAuthorizationRequirement
接口的實現類都繼承了IAuthorizationHandler
接口來提供HandleAsync方法來實現對應的授權檢驗。
下面介紹asp.net core框架里面默認實現的幾種IAuthorizationRequirement
實現類型。
2.5.1 DenyAnonymousAuthorizationRequirement
阻止匿名用戶操作,言外之意就是拒絕未被驗證的匿名用戶訪問資源。
源碼如下:
public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler<DenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
{
var user = context.User;
var userIsAnonymous =
user?.Identity == null ||
!user.Identities.Any(i => i.IsAuthenticated);
if (!userIsAnonymous)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
通過用戶的CliamPrincipal
對象身份是否為空或是否是一個經過認證的用戶身份,以此來確定當前請求的用戶是否來源於匿名用戶。
2.5.2 NameAuthorizationRequirement
指定用戶名的授權方式,判斷當前用戶與某個指定的用戶是否匹配以此來授權訪問資源。
源碼如下:
public class NameAuthorizationRequirement : AuthorizationHandler<NameAuthorizationRequirement>, IAuthorizationRequirement
{
public NameAuthorizationRequirement(string requiredName)
{
if (requiredName == null)
{
throw new ArgumentNullException(nameof(requiredName));
}
RequiredName = requiredName;
}
public string RequiredName { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement)
{
if (context.User != null)
{
if (context.User.Identities.Any(i => string.Equals(i.Name, requirement.RequiredName)))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
其中RequiredName
屬性為授權用戶,通過HandleRequirementAsync
方法進行校驗當前用戶的ClaimPrincipal
對象的身份與RequiredName
是否具有匹配。
這里的判斷用的是 string.Equals() 說明這里比較的用戶名是區別大小寫的。
2.5.3 ClaimsAuthorizationRequirement
基於指定聲明類型的授權策略,檢驗當前用戶是否聲明類型和候選值。
源碼如下:
public class ClaimsAuthorizationRequirement : AuthorizationHandler<ClaimsAuthorizationRequirement>, IAuthorizationRequirement
{
public ClaimsAuthorizationRequirement(string claimType, IEnumerable<string> allowedValues)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
ClaimType = claimType;
AllowedValues = allowedValues;
}
public string ClaimType { get; }
public IEnumerable<string> AllowedValues { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimsAuthorizationRequirement requirement)
{
if (context.User != null)
{
var found = false;
if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
{
found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
}
else
{
found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
&& requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
}
if (found)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
由上我們可以看的出,ClaimType
和AllowedValues
這兩個屬性在構造函數中被初始化,分別用來表示當前聲明的聲明類型和默認允許值。通過HandleRequirementAsync
來授權檢驗是否完成通過。
2.5.4 RolesAuthorizationRequirement
基於角色的授權策略,檢驗當前用戶是否擁有約定匹配的角色,如果擁有,則可以訪問對應的資源。
源碼如下:
public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles)
{
if (allowedRoles == null)
{
throw new ArgumentNullException(nameof(allowedRoles));
}
if (allowedRoles.Count() == 0)
{
throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty);
}
AllowedRoles = allowedRoles;
}
public IEnumerable<string> AllowedRoles { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
{
if (context.User != null)
{
bool found = false;
if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
{
// Review: What do we want to do here? No roles requested is auto success?
}
else
{
found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
}
if (found)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
其中AllowedRoles
表示目標角色列表的集合。通過HandleRequirementAsync
實現授權檢驗,調用IsInRole
方法來判斷當前用戶的ClaimsPrincipal
對象是否有指定的角色。
2.5.5 AssertionRequirement
基於AuthorizationHandlerContext
上下文斷言的形式來聲明授權。
源碼如下:
public class AssertionRequirement : IAuthorizationHandler, IAuthorizationRequirement
{
public Func<AuthorizationHandlerContext, Task<bool>> Handler { get; }
public AssertionRequirement(Func<AuthorizationHandlerContext, bool> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
Handler = context => Task.FromResult(handler(context));
}
public AssertionRequirement(Func<AuthorizationHandlerContext, Task<bool>> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
Handler = handler;
}
public async Task HandleAsync(AuthorizationHandlerContext context)
{
if (await Handler(context))
{
context.Succeed(this);
}
}
}
通過類型為Func<AuthorizationHandlerContext, Task<bool>>
的委托來表示該斷言,利用它來授權驗證。在HandleAsync
檢驗方法中,直接調用這個委托對象來完成判斷。
2.5.6 OperationAuthorizationRequirement
基於預定義操作的授權策略。
源碼如下:
public class OperationAuthorizationRequirement : IAuthorizationRequirement
{
public string Name { get; set; }
}
由上可知,只是包含一個操作名字的Name
屬性,目的在於將授權的目標對象映射到一個預定義的操作上。
三、用例
出現的IAuthorizationRequirement
對象可以發現,授權要求Requirement
屬性是策略的核心方案,每一中Requirement
都代表一種授權方式。
在上文我們通過構建策略AuthorizationPolicyBuilder
對象的源碼可以發現,為我們提供了多個方法由預定義的IAuthorizationRequirement
類型來創建並將其添加到Requirements
集合中。
3.1 應用
實例應用如下:在ConfigureServices中配置服務中
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var combindPolicy = new AuthorizationPolicyBuilder().RequireClaim("role").Build();
services.AddAuthorization(options =>
{
//DenyAnonymousAuthorizationRequirement
options.AddPolicy("DenyAnonyUser", policy => policy.RequireAuthenticatedUser());
//NameAuthorizationRequirement
options.AddPolicy("NameAuth", policy => policy.RequireUserName("艾三元"));
//ClaimsAuthorizationRequirement
options.AddPolicy("ClaimsAuth", policy => policy.RequireClaim("role","admin"));
//RolesAuthorizationRequirement
options.AddPolicy("RolesAuth", policy => policy.RequireRole("admin","user"));
//AssertionRequirement
options.AddPolicy("AssertAuth", policy => policy.RequireAssertion(c=>c.User.HasClaim(o=>o.Type=="role")));
//同樣可可用直接調用Combind方法,策略AuthorizationPolicy
options.AddPolicy("CombindAuth", policy => policy.Combine(combindPolicy));
});
}
以上,分別實現了框架中默認實現的幾種IAuthorizationRequirement
實現類型在實際中的應用,通過不同授權要求實現的策略方式,同時也可以將上面多種方式合並成一個對象,進行調用使用。
3.2 拓展
當然了,除了自帶了這幾種默認實現方式之外,我們也可以通過自定義Requirement
來滿足我們的需求。
這個在上一節初識授權的時候,已經提到了自定義授權這一塊,所以在這里再看一次。
定義一個權限策略
PermissionRequirement
,這個策略並包含一些屬性。public class PermissionRequirement: IAuthorizationRequirement { public string _permissionName { get; } public PermissionRequirement(string PermissionName) { _permissionName = PermissionName; } }
再定義一個策略處理類
public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { var role = context.User.FindFirst(c => c.Type == ClaimTypes.Role); if (role != null) { var roleValue = role.Value; if (roleValue==requirement._permissionName) { context.Succeed(requirement); } } return Task.CompletedTask;
配置使用
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); //基於自定義策略授權 services.AddAuthorization(options => { options.AddPolicy("customizePermisson", policy => policy .Requirements .Add(new PermissionRequirement("admin"))); }); //此外,還需要在 IAuthorizationHandler 類型的范圍內向 DI 系統注冊新的處理程序: services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>(); // 如前所述,要求可包含多個處理程序。如果為授權層的同一要求向 DI 系統注冊多個處理程序,有一個成功就足夠了。 }
特別說明
上述使用的處理程序是一對一的關系,當聲明要求滿足條件的時候,則任務授權成功, 授權成功后,
context.Succeed
將通過滿足要求作為其唯一參數調用。但是授權策略中也包含一對多的要求關系,它們屬於 & 的關系,只用全部驗證通過,才能最終授權成功。但是在有些場景下,我們可能希望一個授權策略可以適用多種情況,比如,我們進入公司時需要出示員工卡才可以被授權進入,但是如果我們忘了帶員工卡,可以去申請一個臨時卡,同樣可以授權成功。
這里貼一個官方文檔的寫法: public class BuildingEntryRequirement : IAuthorizationRequirement { } public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BuildingEntryRequirement requirement) { 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) { 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; } }
我們定義了兩個Handler,但是想讓它們得到執行,還需要將其注冊到DI系統中:
services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
services.AddSingleton<IAuthorizationHandler, TemporaryStickerHandler>();
確保兩個處理程序都已注冊。 如果某個處理程序在某一策略評估后使用context.succeed()
來成功 BuildingEntryRequirement ,則策略評估將成功。但是當我們調用context.Fail()
方法后會將授權結構設置失敗,那樣的話,最后的結果都是會授權失敗的。所以正常情況下。我們都是只設置標記context.succeed()
。
四、說明
這里對上文源碼中出現的一些聲明方法進行說明。
4.1 IAuthorizeData
使用 IAuthorizeDate 接口方法。定義授權規則應用於資源所需的數據集。
public interface IAuthorizeData
{
string Policy { get; set; }
string Roles { get; set; }
string AuthenticationSchemes { get; set; }
}
Policy
:獲取或設置確定對資源的訪問的策略名稱。
Roles
: 獲取或設置以逗號分隔的允許訪問資源的角色列表。
AuthenticationSchemes
: 獲取或以設置以逗號分隔的方案列表,從中可以構造用戶信息。
所以IAuthorizeData
中定義的policy
、roles
、AuthenticationSchemes
三個分別代表着授權系統中的三種授權方式。
具體的使用在后續講解授權的執行流程中會進行詳細介紹。
五、后續
上面主要講解了授權在配置方面的源碼,本來打算繼續接着往下寫的,但是考慮到整體篇幅可能會太長了,不便於閱讀。
所以授權揭秘的上篇內容就說到這里了,在后續的文章中,會繼續深入了解授權內部機制的奧秘以及是如何實現執行授權流程的。