Нет встроенных функций для этого.
Но вы можете легко реализовать пользовательский LogicalOrPolicyProvider
(и также обработчик) для достижения той же цели. LogicalOrPolicyProvider
будет создавать политику динамически в соответствии с именем политики, например:
[Authorize(Policy="Choice: policy='New York'| role= ADMIN")]
Приведенный выше атрибут создаст новую политику, которая должна соответствовать политике «Нью-Йорк» или требует роли ADMIN
Более того, мы могли бы определить некоторые правила для обработки более общих случаев. Допустим, хотим составить следующие требования:
Choice: policy='New York'| role= ADMIN
Choice: policy='New York'| role= 'ADMIN'
Choice: policy='New York'| policy = 'WC' | role= root | role = 'GVN'
Вы можете определять свои собственные правила так, как вам нравится, я лично предпочитаю:
- Начните с токена
Choice
, за которым следует разделитель :
(может иметь несколько необязательных пробелов ' '
)
- Политика определяется
policy=policyName
, если policyName содержит пробел, вы должны окружить его ''
. Вы можете определить многие policy
, как вам нравится
- Роль определяется
role = roleName
. Вы также можете определить столько ролей, сколько захотите.
- Все
policy
& role
определения разделены |
.
Реализация вышеуказанного дизайна:
Давайте определим LogicalOrRequirement
для хранения всех возможных политик:
public class LogicalOrRequirement : IAuthorizationRequirement
{
public IList<AuthorizationPolicy> Policies { get; }
public LogicalOrRequirement(IList<AuthorizationPolicy> policies)
{
this.Policies = policies;
}
}
Если какая-либо из этих политик будет успешной, просто пройдите мимо:
public class LogicalOrAuthorizationHandler : AuthorizationHandler<LogicalOrRequirement>
{
public LogicalOrAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
}
private readonly IHttpContextAccessor _httpContextAccessor;
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, LogicalOrRequirement requirement)
{
var httpContext = this._httpContextAccessor.HttpContext;
var policyEvaluator = httpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
foreach (var policy in requirement.Policies)
{
var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, httpContext);
if (authenticateResult.Succeeded)
{
context.Succeed(requirement);
}
}
}
}
Теперь давайте создадим политику динамически с помощью PolicyProvider
:
public class LogicalOrPolicyProvider : IAuthorizationPolicyProvider
{
const string POLICY_PREFIX = "Choice";
const string TOKEN_POLICY="policy";
const string TOKEN_ROLE="role";
public const string Format = "Choice: policy='p3' | policy='p2' | role='role1' | ...";
private AuthorizationOptions _authZOpts { get; }
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
public LogicalOrPolicyProvider(IOptions<AuthorizationOptions> options )
{
_authZOpts = options.Value;
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
// Choice: policy= | policy= | role= | role = ...
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase))
{
var policyNames = policyName.Substring(POLICY_PREFIX.Length);
var startIndex = policyNames.IndexOf(":");
if(startIndex == -1 || startIndex == policyNames.Length)
{
throw new ArgumentException($"invalid syntax, must contains a ':' before tokens. The correct format is {Format}");
}
// skip the ":" , and turn it into the following list
// [[policy,policyName],[policy,policName],...[role,roleName],...,]
var list= policyNames.Substring(startIndex+1)
.Split("|")
.Select(p => p.Split("=").Select(e => e.Trim().Trim('\'')).ToArray() )
;
// build policy for roleNames
var rolesPolicyBuilder = new AuthorizationPolicyBuilder();
var roleNames =list.Where(arr => arr[0].ToLower() == TOKEN_ROLE)
.Select(arr => arr[1])
.ToArray();
var rolePolicy = rolesPolicyBuilder.RequireRole(roleNames).Build();
// get policies with all related names
var polices1= list.Where(arr => arr[0].ToLower() == TOKEN_POLICY);
var polices=polices1
.Select(arr => arr[1])
.Select(name => this._authZOpts.GetPolicy(name)) // if the policy with the name doesn exit => null
.Where(p => p != null) // filter null policy
.Append(rolePolicy)
.ToList();
var pb= new AuthorizationPolicyBuilder();
pb.AddRequirements(new LogicalOrRequirement(polices));
return Task.FromResult(pb.Build());
}
return FallbackPolicyProvider.GetPolicyAsync(policyName);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return FallbackPolicyProvider.GetDefaultPolicyAsync();
}
}
Наконец, не забудьте зарегистрировать две службы в вашем Startup.cs
:
services.AddSingleton<IAuthorizationPolicyProvider, LogicalOrPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, LogicalOrAuthorizationHandler>();
Теперь, когда вы хотите логическую or
композицию, просто добавьте [Authorize(Policy="Choice: ...")]
:
[Authorize(Policy="Choice: policy='New York'| role= ADMIN")]
public IActionResult Privacy()
{
return View();
}