在 ASP.NET Core 中使用 IAuthorizationPolicyProvider 的自定义授权策略提供程序Custom Authorization Policy Providers using IAuthorizationPolicyProvider in ASP.NET Core

本文内容

作者:Mike Rousos

通常,在使用基于策略的授权时,通过调用 AuthorizationOptions.AddPolicy 作为授权服务配置的一部分来注册策略。在某些情况下,可能无法(或需要)以这种方式注册所有的授权策略。在这些情况下,可以使用自定义 IAuthorizationPolicyProvider 来控制如何提供授权策略。

自定义IAuthorizationPolicyProvider可能有用的方案示例包括:

  • 使用外部服务提供策略评估。
  • 使用大范围的策略(例如,使用不同的房间号或年龄),因此,将每个授权策略添加到 AuthorizationOptions.AddPolicy 调用并无意义。
  • 根据外部数据源(如数据库)中的信息在运行时创建策略,或通过其他机制动态确定授权要求。

AspNetCore GitHub 存储库查看或下载示例代码下载 dotnet/AspNetCore 存储库 ZIP 文件。解压缩文件。导航到src/Security/samples/CustomPolicyProvider项目文件夹。

自定义策略检索Customize policy retrieval

ASP.NET Core 应用使用 IAuthorizationPolicyProvider 接口的实现来检索授权策略。默认情况下, DefaultAuthorizationPolicyProvider已注册并使用。DefaultAuthorizationPolicyProvider 返回 IServiceCollection.AddAuthorization 调用中提供的 AuthorizationOptions 的策略。

自定义此行为,方法是在应用程序的依赖关系注入容器中注册不同的 IAuthorizationPolicyProvider 实现。

IAuthorizationPolicyProvider 接口包含三个 Api:

通过实现这些 Api,你可以自定义如何提供授权策略。

参数化授权特性示例Parameterized authorize attribute example

IAuthorizationPolicyProvider 很有用的一种情况是,启用其要求依赖于参数的自定义 [Authorize] 特性。例如,在基于策略的授权文档中,使用基于时期的("AtLeast21")策略作为示例。如果应用中的不同控制器操作应该在不同年龄段的用户中可用,则使用许多不同的基于时期的策略可能会很有用。您可以使用自定义 IAuthorizationPolicyProvider动态生成策略,而不是注册应用程序 AuthorizationOptions所需的所有基于时期的不同策略。若要更轻松地使用策略,可以使用自定义授权属性(如 [MinimumAgeAuthorize(20)])来批注操作。

自定义授权属性Custom Authorization attributes

授权策略由其名称标识。前面所述的自定义 MinimumAgeAuthorizeAttribute 需要将参数映射到一个字符串,该字符串可用于检索相应的授权策略。为此,可以从 AuthorizeAttribute 派生,并使 Age 属性包装 AuthorizeAttribute.Policy 属性。

  1. internal class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
  2. {
  3. const string POLICY_PREFIX = "MinimumAge";
  4. public MinimumAgeAuthorizeAttribute(int age) => Age = age;
  5. // Get or set the Age property by manipulating the underlying Policy property
  6. public int Age
  7. {
  8. get
  9. {
  10. if (int.TryParse(Policy.Substring(POLICY_PREFIX.Length), out var age))
  11. {
  12. return age;
  13. }
  14. return default(int);
  15. }
  16. set
  17. {
  18. Policy = $"{POLICY_PREFIX}{value.ToString()}";
  19. }
  20. }
  21. }

此属性类型具有基于硬编码前缀("MinimumAge")的 Policy 字符串,以及通过构造函数传入的整数。

可以采用与其他 Authorize 特性相同的方式将其应用于操作,只不过它采用整数作为参数。

  1. [MinimumAgeAuthorize(10)]
  2. public IActionResult RequiresMinimumAge10()

自定义 IAuthorizationPolicyProviderCustom IAuthorizationPolicyProvider

使用自定义 MinimumAgeAuthorizeAttribute 可以轻松地请求所需的最短期限的授权策略。要解决的下一个问题是确保授权策略可用于所有这些不同年龄段。这就是 IAuthorizationPolicyProvider 有用的地方。

使用 MinimumAgeAuthorizationAttribute时,授权策略名称将遵循模式 "MinimumAge" + Age,因此自定义 IAuthorizationPolicyProvider 应通过以下方式生成授权策略:

  • 正在分析策略名称的期限。
  • 使用 AuthorizationPolicyBuilder 创建新 AuthorizationPolicy
  • 在此示例和以下示例中,假定用户通过 cookie 进行身份验证。应至少用一个授权方案名称构造 AuthorizationPolicyBuilder 或始终成功。否则,没有关于如何向用户提供质询的信息,将引发异常。
  • 根据 AuthorizationPolicyBuilder.AddRequirements的年龄将要求添加到策略。在其他情况下,你可能会改用 RequireClaimRequireRoleRequireUserName
  1. internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
  2. {
  3. const string POLICY_PREFIX = "MinimumAge";
  4. // Policies are looked up by string name, so expect 'parameters' (like age)
  5. // to be embedded in the policy names. This is abstracted away from developers
  6. // by the more strongly-typed attributes derived from AuthorizeAttribute
  7. // (like [MinimumAgeAuthorize()] in this sample)
  8. public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
  9. {
  10. if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
  11. int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age))
  12. {
  13. var policy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme);
  14. policy.AddRequirements(new MinimumAgeRequirement(age));
  15. return Task.FromResult(policy.Build());
  16. }
  17. return Task.FromResult<AuthorizationPolicy>(null);
  18. }
  19. }

多个授权策略提供程序Multiple authorization policy providers

使用自定义 IAuthorizationPolicyProvider 实现时,请记住 ASP.NET Core 只使用 IAuthorizationPolicyProvider的一个实例。如果自定义提供程序无法为将使用的所有策略名称提供授权策略,则该提供程序应遵从备份提供程序。

例如,假设某个应用程序需要自定义年龄策略和更传统的基于角色的策略检索。此类应用程序可使用自定义授权策略提供程序,该提供程序:

  • 尝试分析策略名称。
  • 如果策略名称不包含 age,则调入到不同的策略提供程序(如 DefaultAuthorizationPolicyProvider)。

可以通过在其构造函数中创建备份策略提供程序来更新上面所示的示例 IAuthorizationPolicyProvider 实现使用 DefaultAuthorizationPolicyProvider (如果策略名称与 "MinimumAge" + age 的预期模式不符,则使用此方法)。

  1. private DefaultAuthorizationPolicyProvider BackupPolicyProvider { get; }
  2. public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options)
  3. {
  4. // ASP.NET Core only uses one authorization policy provider, so if the custom implementation
  5. // doesn't handle all policies it should fall back to an alternate provider.
  6. BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
  7. }

然后,可以将 GetPolicyAsync 方法更新为使用 BackupPolicyProvider,而不是返回 null:

  1. ...
  2. return BackupPolicyProvider.GetPolicyAsync(policyName);

默认策略Default policy

自定义 IAuthorizationPolicyProvider 除了提供命名的授权策略外,还需要实现 GetDefaultPolicyAsync,以提供 [Authorize] 属性的授权策略,而无需指定策略名称。

在许多情况下,此授权特性只需要经过身份验证的用户,因此,你可以通过调用 RequireAuthenticatedUser来执行必要的策略:

  1. public Task<AuthorizationPolicy> GetDefaultPolicyAsync() =>
  2. Task.FromResult(new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build());

与自定义 IAuthorizationPolicyProvider的所有方面一样,你可以根据需要对其进行自定义。在某些情况下,可能需要从回退 IAuthorizationPolicyProvider检索默认策略。

回退策略Fallback policy

自定义 IAuthorizationPolicyProvider 可以选择实现 GetFallbackPolicyAsync 来提供在结合策略时使用的策略,以及未指定策略时使用的策略。如果 GetFallbackPolicyAsync 返回非 null 策略,则在未为请求指定策略时,授权中间件会使用返回的策略。

如果不需要回退策略,则提供程序可以返回 null 或将其推迟到回退提供程序:

  1. public Task<AuthorizationPolicy> GetFallbackPolicyAsync() =>
  2. Task.FromResult<AuthorizationPolicy>(null);

使用自定义 IAuthorizationPolicyProviderUse a custom IAuthorizationPolicyProvider

若要从 IAuthorizationPolicyProvider使用自定义策略,你必须:

  • 与所有基于策略的授权方案一起,用依赖关系注入注册适当的 AuthorizationHandler 类型(如基于策略的授权中所述)。
  • 在应用程序的依赖关系注入服务集合中注册自定义 IAuthorizationPolicyProvider 类型(在 Startup.ConfigureServices中)以替换默认策略提供程序。
  1. services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();

Aspnet/AuthSamples GitHub 存储库中提供了一个完整的自定义 IAuthorizationPolicyProvider 示例。