【问题标题】:ASP.NET Core Authorization: Combining OR requirementsASP.NET Core 授权:结合 OR 要求
【发布时间】:2018-08-29 14:34:15
【问题描述】:

我不确定如何在 ASP.NET Core 授权中实现组合的“OR”要求。在以前的 ASP.NET 版本中,这将通过角色来完成,但我试图通过声明来做到这一点,部分原因是为了更好地理解它。

用户有一个名为 AccountType 的枚举,它将提供对控制器/操作/等的不同级别的访问。类型分为三个级别,分别称为 User、BiggerUser 和 BiggestUser。所以 BiggestUser 可以访问他们下面的帐户类型所拥有的所有内容,依此类推。我想通过使用策略的授权标签来实现这一点。

所以首先我有一个要求:

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 个策略 strongA 和 strongB。它们分别需要 claimA 和 claimB。您需要一个为已经可以使用 strongA 或 strongB 的用户完成的策略。然后,我想到的解决方案是: - 1) 有一个新的声明 claimC - 2) 更改 strongA 和 strongB 以便它们都需要 claimC。 - 3) 将claimC 授予已经拥有claimA 或claimB 的所有用户(现在您又回到了原来的设置)。最后 。 4) 创建新的策略 lightC ,只需要 claimC -> 现在所有满足策略 strongA 或 strongB 的用户也将满足策略 lightC
  • 为了让这个项目继续进行,我现在只在一个处理程序中完成这一切,这只是一组非常不雅的 if(this requirements){check for these type} 对每种帐户类型重复一次。

标签: asp.net-core-2.0


【解决方案1】:

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

不过,我认为该解决方案不适用于您的问题;我可以看到两种很好的实现方式


TypeRequirement中提供多个AccountTypes

然后,该要求将保存所有满足该要求的值。

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 必须满足的最低值,然后处理程序将其与用户的帐户类型进行比较。

枚举可以与&lt;=&gt;= 运算符进行比较,也可以使用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)));

【讨论】:

  • 很好的答案,但我认为您的意思是options.AddPolicy("HostRights", policy =&gt; policy.AddRequirements(new AccountTypeRequirement(new AccountTypes []{ AccountTypes.Host, AccountTypes.Admin, AccountTypes.SuperAdmin }))); 等等?
  • @greedyLump 在这种情况下两种语法都是正确的;我使用的那个是允许的,因为在参数声明中使用了params 关键字(params AccountType[] accounts)。你可以阅读更多关于它的信息on this official documentation page
  • 我错过了 params 关键字。无论哪种方式,我都使用了枚举实现,最后只需要更改几行代码
【解决方案2】:

从我的original answer 复制给那些寻找简短答案的人(注意:以下解决方案不解决层次结构问题)。

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

例如。我只希望“John Doe”、“Jane Doe”用户查看“结束合同”屏幕或仅来自“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)))));
});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-25
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 1970-01-01
    • 2018-04-03
    • 2020-06-10
    相关资源
    最近更新 更多