.Net Core [Authorize] - 或者代替 And 进行权限测试

.Net Core [Authorize] - Or instead of And for permission test

如果用户同时是 "admin" 的 Role AND 和 "New York" 的 Policy,则以下代码授权过滤器通过。我如何将其更改为 "admin" OR "New York"

[Authorize(Policy = "New York", Roles = "admin")]

在这种特定情况下,我想要一个 OR 语句,而不是 AND

没有 built-in 函数可以做到这一点。

但您可以轻松实现自定义 LogicalOrPolicyProvider(以及处理程序)来实现相同的目标。 LogicalOrPolicyProvider会根据策略名称动态构建策略,例如:

[Authorize(Policy="Choice: policy='New York'| role= ADMIN")]

以上属性将生成一个新策略,该策略应满足 'New York' 的策略或需要 ADMIN

的角色

此外,我们可以定义一些规则来处理更一般的情况。假设要编写以下要求:

Choice: policy='New York'| role= ADMIN
Choice: policy='New York'| role= 'ADMIN'
Choice: policy='New York'| policy = 'WC' | role= root | role = 'GVN'

你可以根据自己的喜好定义自己的规则,我个人更喜欢:

  1. 以标记 Choice 开头,后跟分隔符 :(可能有几个可选的 space 字符 ' '
  2. 一个策略由policy=policyName定义,如果策略名称包含space,你应该用''包围它。您可以根据需要定义多个 policy
  3. 该角色由 role = roleName 定义。您还可以定义任意数量的角色。
  4. 所有 policyrole 定义由 | 分隔。

上述设计的实现:

让我们定义一个 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();
}