ASP.NET Core 中使用 IAuthorizationPolicyProvider 的自定义授权策略提供程序
作者:Mike Rousos
通常在使用基于策略的授权时,通过在授权服务配置中调用 AuthorizationOptions.AddPolicy
来注册策略。 在某些情况下,可能无法(或不可取)采用此方式注册所有授权策略。 在这些情况下,可以使用自定义 IAuthorizationPolicyProvider
来控制如何提供授权策略。
自定义 IAuthorizationPolicyProvider 可能很有用的方案示例包括:
- 使用外部服务提供策略评估。
- 使用大范围的策略(例如对于不同房间号或年龄),因此使用
AuthorizationOptions.AddPolicy
调用添加每个单独授权策略没有意义。 - 在运行时基于外部数据源(如数据库)中的信息创建策略,或通过其他机制动态确定授权要求。
在 AspNetCore GitHub 存储库中查看或下载示例代码。 下载 dotnet/AspNetCore 存储库 ZIP 文件。 解压缩文件。 导航到 src/Security/samples/CustomPolicyProvider 项目文件夹。
自定义策略检索
ASP.NET Core 应用使用 IAuthorizationPolicyProvider
接口的实现检索授权策略。 默认情况下, DefaultAuthorizationPolicyProvider 注册和使用。 DefaultAuthorizationPolicyProvider
从 IServiceCollection.AddAuthorization
调用中提供的 AuthorizationOptions
返回策略。
可通过在应用的依赖项注入容器中注册不同 IAuthorizationPolicyProvider
实现来自定义此行为。
IAuthorizationPolicyProvider
接口包含三个 API:
- GetPolicyAsync 返回给定名称的授权策略。
- GetDefaultPolicyAsync 返回默认授权策略, (用于
[Authorize]
属性的策略,而不指定策略) 。 - GetFallbackPolicyAsync 如果未) 指定策略,则返回授权中间件使用的策略 (回退授权策略。
通过实现这些 API,可以自定义如何提供授权策略。
参数化授权属性示例
IAuthorizationPolicyProvider
十分有用的一个方案是启用其要求取决于参数的自定义 [Authorize]
属性。 例如,在基于策略的授权文档中,将基于年龄(“AtLeast21”)的策略用作示例。 如果应用中的不同控制器操作应供不同年龄的用户使用,则使用许多不同的基于年龄的策略可能十分有用。 可以使用自定义 AuthorizationOptions
动态生成策略,而不是在 IAuthorizationPolicyProvider
中注册应用程序需要的所有基于年龄的不同策略。 若要更轻松地使用策略,可以使用自定义授权属性(如 [MinimumAgeAuthorize(20)]
)对操作进行批注。
自定义授权属性
授权策略通过名称进行标识。 前面所述的自定义 MinimumAgeAuthorizeAttribute
需要将参数映射到可用于检索对应授权策略的字符串。 为此,可以从 AuthorizeAttribute
派生并让 Age
属性包装 AuthorizeAttribute.Policy
属性。
internal class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
{
const string POLICY_PREFIX = "MinimumAge";
public MinimumAgeAuthorizeAttribute(int age) => Age = age;
// Get or set the Age property by manipulating the underlying Policy property
public int Age
{
get
{
if (int.TryParse(Policy.Substring(POLICY_PREFIX.Length), out var age))
{
return age;
}
return default(int);
}
set
{
Policy = $"{POLICY_PREFIX}{value.ToString()}";
}
}
}
此属性类型具有一个基于硬编码前缀 ("MinimumAge"
) 的 Policy
字符串和一个通过构造函数传入的整数。
可以采用与其他 Authorize
属性相同的方式将它应用于操作,只不过它采用整数作为参数。
[MinimumAgeAuthorize(10)]
public IActionResult RequiresMinimumAge10()
自定义 IAuthorizationPolicyProvider
通过自定义 MinimumAgeAuthorizeAttribute
可以轻松地为所需的任何最小年龄请求授权策略。 下一个要解决的问题是确保授权策略可用于所有这些不同的年龄。 这便是 IAuthorizationPolicyProvider
的用武之地。
使用 MinimumAgeAuthorizationAttribute
时,授权策略名称会遵循模式 "MinimumAge" + Age
,因此自定义 IAuthorizationPolicyProvider
应通过以下方式生成授权策略:
- 从策略名称分析年龄。
- 使用
AuthorizationPolicyBuilder
创建新AuthorizationPolicy
- 在此示例和下面的示例中,假定用户通过 cookie 进行身份验证。
AuthorizationPolicyBuilder
应使用至少一个授权方案名称进行构造或是始终成功。 否则,不会提供任何有关如何向用户提供质询的信息,会引发异常。 - 使用
AuthorizationPolicyBuilder.AddRequirements
基于年龄向策略添加要求。 在其他方案中,可以改为使用RequireClaim
、RequireRole
或RequireUserName
。
internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
{
const string POLICY_PREFIX = "MinimumAge";
// Policies are looked up by string name, so expect 'parameters' (like age)
// to be embedded in the policy names. This is abstracted away from developers
// by the more strongly-typed attributes derived from AuthorizeAttribute
// (like [MinimumAgeAuthorize()] in this sample)
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age))
{
var policy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme);
policy.AddRequirements(new MinimumAgeRequirement(age));
return Task.FromResult(policy.Build());
}
return Task.FromResult<AuthorizationPolicy>(null);
}
}
多个授权策略提供程序
使用自定义 IAuthorizationPolicyProvider
实现时,请记住,ASP.NET Core 只使用 IAuthorizationPolicyProvider
的一个实例。 如果自定义提供程序无法为将使用的所有策略名称提供授权策略,则它应遵从备份提供程序。
例如,假设应用程序需要自定义年龄策略和更传统的基于角色的策略检索。 此类应用可以使用执行以下操作的自定义授权策略提供程序:
- 尝试分析策略名称。
- 如果策略名称不包含年龄,则调入其他策略提供程序(例如
DefaultAuthorizationPolicyProvider
)。
上面所示的示例 IAuthorizationPolicyProvider
实现可以通过在其构造函数中创建备份策略提供程序(在策略名称不匹配“最小年龄”+ 年龄的预期模式时使用),更新为使用 DefaultAuthorizationPolicyProvider
。
private DefaultAuthorizationPolicyProvider BackupPolicyProvider { get; }
public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options)
{
// ASP.NET Core only uses one authorization policy provider, so if the custom implementation
// doesn't handle all policies it should fall back to an alternate provider.
BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
随后 GetPolicyAsync
方法可以更新为使用 BackupPolicyProvider
,而不是返回 null:
...
return BackupPolicyProvider.GetPolicyAsync(policyName);
默认策略
除了提供命名授权策略外,自定义 IAuthorizationPolicyProvider
需要实现 GetDefaultPolicyAsync
,以便在未指定策略名称的情况下为 [Authorize]
属性提供授权策略。
在许多情况下,此授权属性只需要经过身份验证的用户,因此可以通过调用 RequireAuthenticatedUser
来创建所需策略:
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() =>
Task.FromResult(new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build());
与自定义 IAuthorizationPolicyProvider
的所有方面一样,可以根据需要对此进行自定义。 在某些情况下,可能需要从回退 IAuthorizationPolicyProvider
检索默认策略。
回退策略
自定义 IAuthorizationPolicyProvider
可以选择实现 GetFallbackPolicyAsync
,以提供在合并策略和未指定策略时使用的策略。 如果 GetFallbackPolicyAsync
返回非 null 策略,则当没有为请求指定策略时,授权中间件会使用返回的策略。
如果不需要回退策略,则提供程序可以返回 null
或遵从回退提供程序:
public Task<AuthorizationPolicy> GetFallbackPolicyAsync() =>
Task.FromResult<AuthorizationPolicy>(null);
使用自定义 IAuthorizationPolicyProvider
若要从中 IAuthorizationPolicyProvider
使用自定义策略, 必须:
与所有基于策略的授权方案一样,使用依赖项注入(在基于策略的授权中进行了介绍)注册相应的
AuthorizationHandler
类型。在
Startup.ConfigureServices
内的应用依赖项注入服务集合中注册自定义IAuthorizationPolicyProvider
类型,以替换默认策略提供程序。services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
完整的自定义 IAuthorizationPolicyProvider
示例可在 dotnet/aspnetcore GitHub 存储库中获得。