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的构造函数中。

 

 

posted @ 2020-01-20 11:06  wall-ee  阅读(515)  评论(0编辑  收藏  举报