Asp.net-Core-2.0

ASP.NET Core 授權:結合 OR 要求

  • November 20, 2019

我不確定如何在 ASP.NET Core 授權中實現組合的“或”要求。在以前的 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 條件,我的處理程序將起作用。那麼我是在正確的軌道上還是有一種不同的方式來實現這個更有意義?

當您想要實現OR邏輯時,官方文件有一個專門的部分。他們提供的解決方案是針對一項要求註冊多個授權處理程序。在這種情況下,所有處理程序都執行,並且如果至少有一個處理程序成功,則認為滿足要求。

不過,我認為該解決方案不適用於您的問題;我可以看到兩種很好地實現這一點的方法


提供多個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,然後處理程序將其與使用者的帳戶類型進行比較。

列舉可以與<=and>=運算符進行比較,也可以使用CompareTo方法。我無法快速找到關於此的可靠文件,但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)));

引用自:https://stackoverflow.com/questions/49389970