asp.net core 授权(1)
IAuthorizeDate接口代表了授权系统的源头:
public interface IAuthorizeData { string Policy { get; set; } string Roles { get; set; } string AuthenticationSchemes { get; set; } }
接口中定义的三个属性分别代表了三种授权类型:
1、基于角色的授权:
[Authorize(Roles = "Admin")] // 多个Role可以使用,分割 public class SampleDataController : Controller { ... }
2、基于scheme的授权:
[Authorize(AuthenticationSchemes = "Cookies")] // 多个Scheme可以使用,分割 public class SampleDataController : Controller { ... }
3、基于策略的授权:
[Authorize(Policy = "EmployeeOnly")] public class SampleDataController : Controller { }
基于策略的授权是授权的核心,使用这种授权策略时,首先要定义策略:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthorization(options => { options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber")); }); }
授权策略本质上就是对claims的一系列断言。
而基于角色和基于scheme的授权都是一种语法糖,最终会转换为策略授权。
详解
整个asp.net core的授权配置通过一个AddAuthorization来进行的:
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 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 IServiceCollection AddAuthorizationCore( this IServiceCollection services, Action<AuthorizationOptions> configure) { if (services == null) throw new ArgumentNullException(nameof (services)); if (configure != null) services.Configure<AuthorizationOptions>(configure); return services.AddAuthorizationCore(); }
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 AddAuthorizationPolicyEvaluator( this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof (services)); services.TryAddSingleton<AuthorizationPolicyMarkerService>(); services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>()); return services; }
AuthorizationOptions
典型的Options模式,设置和获取授权策略的入口点
public class AuthorizationOptions { private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = (IDictionary<string, AuthorizationPolicy>) new Dictionary<string, AuthorizationPolicy>((IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase); public bool InvokeHandlersAfterFailure { get; set; } = true; public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder(Array.Empty<string>()).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)); this.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)); AuthorizationPolicyBuilder authorizationPolicyBuilder = new AuthorizationPolicyBuilder(Array.Empty<string>()); configurePolicy(authorizationPolicyBuilder); this.PolicyMap[name] = authorizationPolicyBuilder.Build(); } public AuthorizationPolicy GetPolicy(string name) { if (name == null) throw new ArgumentNullException(nameof (name)); return !this.PolicyMap.ContainsKey(name) ? (AuthorizationPolicy) null : this.PolicyMap[name]; } }
这个类中的几个重要的属性和方法:
1、PolicyMap,类型为IDictionary<string, AuthorizationPolicy>,维护一个授权策略的字典。
2、DefaultPolicy ,类型为AuthorizationPolicy,这个默认的策略要求用户必须登录
3、FallbackPolicy ,类型为AuthorizationPolicy,这个用来作为后备的授权策略
4、AddPolicy(),用来维护PolicyMap
5、GetPolicy(),用来根据名称获取相应的Policy,获取不到会返回null
综上,AuthorizationOptions就是用来设置和获取授权策略的。
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<IAuthorizationRequirement>() == 0) throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty); this.Requirements = (IReadOnlyList<IAuthorizationRequirement>) new List<IAuthorizationRequirement>(requirements).AsReadOnly(); this.AuthenticationSchemes = (IReadOnlyList<string>) 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 AuthorizationPolicy.Combine((IEnumerable<AuthorizationPolicy>) policies); } public static AuthorizationPolicy Combine( IEnumerable<AuthorizationPolicy> policies) { if (policies == null) throw new ArgumentNullException(nameof (policies)); AuthorizationPolicyBuilder authorizationPolicyBuilder = new AuthorizationPolicyBuilder(Array.Empty<string>()); foreach (AuthorizationPolicy policy in policies) authorizationPolicyBuilder.Combine(policy); return authorizationPolicyBuilder.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)); bool flag1 = false; if (authorizeData is IList<IAuthorizeData> authorizeDataList) flag1 = authorizeDataList.Count == 0; AuthorizationPolicyBuilder policyBuilder = (AuthorizationPolicyBuilder) null; if (!flag1) { foreach (IAuthorizeData authorizeData1 in authorizeData) { IAuthorizeData authorizeDatum = authorizeData1; if (policyBuilder == null) policyBuilder = new AuthorizationPolicyBuilder(Array.Empty<string>());
//Avoid allocating enumerator if the data is known to be empty bool flag2 = true; if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy)) { AuthorizationPolicy policyAsync = await policyProvider.GetPolicyAsync(authorizeDatum.Policy); if (policyAsync == null) throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound((object) authorizeDatum.Policy)); policyBuilder.Combine(policyAsync); flag2 = false; } string[] strArray1 = authorizeDatum.Roles?.Split(',', StringSplitOptions.None); if (strArray1 != null && ((IEnumerable<string>) strArray1).Any<string>()) { policyBuilder.RequireRole(((IEnumerable<string>) strArray1).Where<string>((Func<string, bool>) (r => !string.IsNullOrWhiteSpace(r))).Select<string, string>((Func<string, string>) (r => r.Trim()))); flag2 = false; } string[] strArray2 = authorizeDatum.AuthenticationSchemes?.Split(',', StringSplitOptions.None); if (strArray2 != null && ((IEnumerable<string>) strArray2).Any<string>()) { foreach (string str in strArray2) { if (!string.IsNullOrWhiteSpace(str)) policyBuilder.AuthenticationSchemes.Add(str.Trim()); } } if (flag2) { AuthorizationPolicyBuilder authorizationPolicyBuilder = policyBuilder; authorizationPolicyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync()); authorizationPolicyBuilder = (AuthorizationPolicyBuilder) null; } authorizeDatum = (IAuthorizeData) null; } } if (policyBuilder == null) { AuthorizationPolicy fallbackPolicyAsync = await policyProvider.GetFallbackPolicyAsync(); if (fallbackPolicyAsync != null) return fallbackPolicyAsync; } return policyBuilder?.Build(); } }
AuthorizationPolicy维护了两个只读的列表,一个是IAuthorizationRequirement的,另一个是string类型的,表示AuthenticationSchemes。又两个方法,一个是Combine(),这个方法是直接通过AuthorizePolicyBuilder来build的,还有一个CombineAsync方法,则是将我们上面介绍的IAuthorizeData转换成AuthorizePolicy,所以说,基于角色的授权和基于方案(AuthenticationScheme)的授权都会转换成基于策略的授权。
CombineAsync方法执行流程如下:
1、对传入的参数(IAuthorizationPolicyProvider 和IEnumerable<IAuthorizeData>)进行断言,不能为空,否则抛出异常。
2、声明一个布尔类型变量(上面是flag1),表示如果已知数据(IEnumerable<IAuthorizeData>)为空,则避免分配枚举数(Avoid allocating enumerator if the data is known to be empty),这个值的设置方式为对传入的authorizeData参数进行校验,首先判断是否为IList<T>,然后判断它的Count属性是否等于0。两者都为true则这个布尔值为true。
3、对第二步声明的这个布尔值进行判断,如果为false,那么进入方法体:
①对传入的authorizeData进行迭代,迭代方法内部做如下处理:
①-①初始化一个AuthorizationPolicyBuilder,声明一个布尔变量(flag2),默认值为true,这个变量表示是否使用默认的授权策略(useDefaultPolicy)。
①-②判断每一个迭代变量(authorizeDatum),如果Policy属性不为空,那么进入方法体,利用传入的policyProvider的GetPolicyAsync()方法,将authorizeDatum的Policy作为参数传入拿到一个AuthorizePolicy并赋值给一 个policyAsync变量。如果这个policyAsync为空,那么就抛出异常。如果不为空,那么利用①-①初始化的这个AuthorizePolicyBuilder来Combine这个获取到的policyAsync。然后将①-①中声明的flag2设置为false。这两步就将authorizeDatum这个迭代变量中的Policy情况处理完了。
①-③继续处理authorizeDatum中的roles,还是利用AuthorizationPolicyBuilder的RequireRoles将这个IAuthorizeData接口中的Roles处理
①-④继续处理authorizeDatum中的AuthenticationSchemes,同样利用AuthorizationPolicyBuilder将这个IAuthorizeData接口中的AuthenticationSchemes处理
①-⑤判断在①-①中声明的flag2,如果这个布尔值还是True,那么就用①-①中声明的AuthorizationPolicyBuilder来初始化一个默认的AuthorizationPolicy,这个默认的AuthorizationPolicy就是一个查看用户是否认证过的policy。至于这个flag2标记,会在检查过每一个迭代变量authorizeDatum的policy和roles之后进行相应的设置,如果authorizeDatum的policy和roles都没有,那么这个值就保持为true,反之会被设置为false。
至此都是CombineAsync这个方法传入的这个authorizeData参数有值的情况下,如果传入的authorizeData没有值,那么从传入的policyProvider参数中拿到一个FallBackPolicy,由于policyProvider依赖于AuthorizatoinOptions,所以对PolicyProvider进行的操作基本都是对AuthorizationOptions的操作。
总结:AuthorizationPolicyBuilder在Build方法执行前一直在维护两个关键的属性:
public IList<IAuthorizationRequirement> Requirements { get; set; } = (IList<IAuthorizationRequirement>) new List<IAuthorizationRequirement>(); public IList<string> AuthenticationSchemes { get; set; } = (IList<string>) new List<string>();
这两个属性也是IAuthorizationPolicy中的两个属性,AuthorizationPolicyBuilder装配一个AuthorizationPolicy的方式有三种,一个是Combine(),这个是将传入的一个Policy进行拆解,将它里面的这两个属性添加到AuthorizationPolicyBuilder中,一个是各种RequireXXXX方法体现出来的,最终都是创建一个IAuthorizationRequirment,添加在自身的Requirements属性中,还有一个是AddAuthenticationSchemes(),将 代表scheme的字符串添加到自身的AuthenticationSchemes属性中,当Build方法执行时,直接新建一个AuthorizationPolicy,将自身维护的这两个属性传入AuthorizationPolicy的构造函数中。