ASP.NET CORE授权:结合OR要求

时间:2018-03-20 16:52:37

标签: asp.net-core-2.0

我不确定如何实现这一点。在asp.net的早期版本中,这可以用角色完成,但我试图用声明来做,部分是为了更好地理解它。用户有一个名为AccountType的枚举,它将提供对控制器/操作/等的不同级别的访问。有三种类型的类型,称为User,BiggerUser和BiggestUser。所以BiggestUser可以访问他下面的帐户类型等等。我想使用策略通过Authorize标签实现。 所以首先我有一个要求:

public class TypeRequirement : IAuthorizationRequirement
{
    public TypeRequirement(AccountTypes account)
    {
        Account = account;
    }

    public AccountTypes Account { get; }
}

我制定了政策:

services.AddAuthorization(options =>
{
    options.AddPolicy("UserRights", policy => 
        policy.AddRequirements(new TypeRequirement(AccountTypes.User));
});

广义处理程序:

public class TypeHandler : AuthorizationHandler<TypeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TypeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == "AccountTypes"))
        { 
            context.Fail();
        }

        string claimValue = context.User.FindFirst(c => c.Type == "AccountTypes").Value;
        AccountTypes claimAsType = (AccountTypes)Enum.Parse(typeof(AccountTypes), claimValue);
        if (claimAsType == requirement.Account)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

我要做的是在政策中加入多项要求,以便任何人都能满足。但我目前的理解是,如果我做了类似的事情:

options.AddPolicy("UserRights", policy =>
    policy.AddRequirements(new TypeRequirement(AccountTypes.User), new TypeRequirement(AccountTypes.BiggerUser));

必须满足两个要求。如果在AddRequirements中有某些东西指定OR条件,我的处理程序将起作用。我是在正确的轨道上还是有一种不同的方式来实现这一点更有意义?

2 个答案:

答案 0 :(得分:1)

当您想要实现 OR 逻辑时,官方文档有dedicated section。他们提供的解决方案是针对一个要求注册多个授权处理程序。在这种情况下,如果至少有一个处理程序成功,则运行所有处理程序并认为满足要求。

我不认为解决方案适用于您的问题;我可以看到两种很好地实现这个的方法

AccountTypes

中提供多个TypeRequirement

然后,该要求将包含满足要求的所有值。

public class TypeRequirement : IAuthorizationRequirement
{
    public TypeRequirement(params AccountTypes[] accounts)
    {
        Accounts = accounts;
    }

    public AccountTypes[] Accounts { get; }
}

然后,处理程序验证当前用户是否与定义的帐户类型之一匹配

public class TypeHandler : AuthorizationHandler<TypeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TypeRequirement requirement)
    {

        if (!context.User.HasClaim(c => c.Type == "AccountTypes"))
        { 
            context.Fail();
            return Task.CompletedTask;
        }

        string claimValue = context.User.FindFirst(c => c.Type == "AccountTypes").Value;
        AccountTypes claimAsType = (AccountTypes)Enum.Parse(typeof(AccountTypes),claimValue);
        if (requirement.Accounts.Any(x => x == claimAsType))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

这允许您创建几个使用相同要求的策略,除非您为每个策略定义AccountTypes的有效值

options.AddPolicy(
    "UserRights",
    policy => policy.AddRequirements(new TypeRequirement(AccountTypes.User, AccountTypes.BiggerUser, AccountTypes.BiggestUser)));

options.AddPolicy(
    "BiggerUserRights",
    policy => policy.AddRequirements(new TypeRequirement(AccountTypes.BiggerUser, AccountTypes.BiggestUser)));

options.AddPolicy(
    "BiggestUserRights",
    policy => policy.AddRequirements(new TypeRequirement(AccountTypes.BiggestUser)));

使用枚举比较功能

正如您在问题中所说,处理AccountTypes的不同值的方式中存在层次结构

  • User可以访问某些内容;
  • BiggerUser可以访问User有权访问的所有内容,以及其他一些内容;
  • BiggestUser可以访问所有内容

然后,我们的想法是,需求将定义必须满足的AccountTypes最低值,然后处理程序将其与用户的帐户类型进行比较。

可以将枚举与<=>=运算符进行比较,也可以使用CompareTo方法进行比较。我无法快速找到有关此的强大文档,但this code sample on docs.microsoft.com显示了低于或等于运算符的用法。

要利用此功能,枚举值需要与您期望的层次结构相匹配,例如:

public enum AccountTypes
{
    User = 1,
    BiggerUser = 2,
    BiggestUser = 3
}

public enum AccountTypes
{
    User = 1,
    BiggerUser, // Automatiaclly set to 2 (value of previous one + 1)
    BiggestUser // Automatically set to 3
}

需求的代码,处理程序和策略的声明将如下所示:

public class TypeHandler : AuthorizationHandler<TypeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TypeRequirement requirement)
    {

        if (!context.User.HasClaim(c => c.Type == "AccountTypes"))
        { 
            context.Fail();
            return Task.CompletedTask;
        }

        string claimValue = context.User.FindFirst(c => c.Type == "AccountTypes").Value;
        AccountTypes claimAsType = (AccountTypes)Enum.Parse(typeof(AccountTypes),claimValue);
        if (claimAsType >= requirement.MinimumAccount)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
options.AddPolicy(
    "UserRights",
    policy => policy.AddRequirements(new TypeRequirement(AccountTypes.User)));

options.AddPolicy(
    "BiggerUserRights",
    policy => policy.AddRequirements(new TypeRequirement(AccountTypes.BiggerUser)));

options.AddPolicy(
    "BiggestUserRights",
    policy => policy.AddRequirements(new TypeRequirement(AccountTypes.BiggestUser)));

答案 1 :(得分:0)

从我的original answer中抄录给那些寻求简短答案的人(注意:以下解决方案无法解决层次结构问题)。

您可以在Startup.cs中添加 OR 条件:

例如我只希望“ John Doe”,“ Jane Doe”用户查看“ Ending Contracts”屏幕,或者仅来自“ MIS”部门的任何人也可以访问同一屏幕。以下内容对我有用,我在其中声明了“部门”和“用户名”的类型:

services.AddAuthorization(options => {
    options.AddPolicy("EndingContracts", policy =>
        policy.RequireAssertion(context => context.User.HasClaim(c => (c.Type == "department" && c.Value == "MIS" ||
        c.Type == "UserName" && "John Doe, Jane Doe".Contains(c.Value)))));
});