Dot-Net

.net 中基於資源的授權

  • June 2, 2018

假設您有一個帶有 GetResource(int resourceId) 操作的 .net web api。此操作(具有指定的 id)應該只授權給與該 id 關聯的使用者(例如,資源可以是使用者撰寫的部落格文章)。

這可以通過多種方式解決,但下面給出一個範例。

   public Resource GetResource(int id)
   {
       string name = Thread.CurrentPrincipal.Identity.Name;
       var user = userRepository.SingleOrDefault(x => x.UserName == name);
       var resource = resourceRepository.Find(id);

       if (resource.UserId != user.UserId)
       {
           throw new HttpResponseException(HttpStatusCode.Unauthorized);
       }

       return resource;
   }

使用者已通過某種機制的身份驗證。

現在,假設我還希望某個使用者(例如,管理員類型)被授權使用端點(具有相同的 id)。此使用者與資源沒有任何直接關係,但由於其類型(或角色)而具有授權。這可以通過檢查使用者是否為管理員類型並返回資源來解決。

有沒有辦法以某種方式集中它,這樣我就不必在每個操作中編寫授權程式碼?

編輯 根據答案,我認為我必須澄清我的問題。

我真正追求的是某種機制,可以使基於資源的授權成為可能,但同時允許一些使用者也使用相同的端點和相同的資源。下面的操作將為這個特定的端點和這個特定的角色(管理員)解決這個問題。

   public Resource GetResource(int id)
   {
       string name = Thread.CurrentPrincipal.Identity.Name;
       var user = userRepository.SingleOrDefault(x => x.UserName == name);
       var resource = resourceRepository.Find(id);

       if (!user.Roles.Any(x => x.RoleName == "Admin" || resource.UserId != user.UserId)
       {
           throw new HttpResponseException(HttpStatusCode.Unauthorized);
       }

       return resource;
   }

我追求的是一些通用的方法來解決這個問題,這樣我就不必編寫兩個具有相同目的的不同端點或在每個端點中編寫資源特定的程式碼。

對於基於資源的授權,我建議使用基於聲明的身份並將使用者 ID 作為聲明嵌入。編寫一個擴展方法來從身份中讀取聲明。所以範常式式碼將如下所示:

public Resource GetResource(int id)
{
    var resource = resourceRepository.Find(id);
   if (resource.UserId != User.Identity.GetUserId())
   {
       throw new HttpResponseException(HttpStatusCode.Unauthorized);
   }

   return resource;
}

如果您想進一步簡化程式碼,您可以編寫一個 UserRepository,它知道使用者數據和資源儲存庫來集中程式碼。程式碼將如下所示:

public Resource GetResource(int id)
{
   return User.Identity.GetUserRepository().FindResource(id);
}

對於基於角色的授權,AuthorizeAttribute將是處理它的最佳位置,您最好為此使用單獨的操作或控制器。

[Authorize(Roles = "admin")]
public Resource GetResourceByAdmin(int id)
{
   return resourceRepository.Find(id);
}

$$ Edit $$ 如果 OP 確實想使用一個操作來處理不同類型的使用者,我個人更喜歡使用使用者儲存庫工廠。操作程式碼將是:

public Resource GetResource(int id)
{
   return User.GetUserRepository().FindResource(id);
}

擴展方法將是:

public static IUserRepository GetUserRepository(this IPrincipal principal)
{
   var resourceRepository = new ResourceRepository();
   bool isAdmin = principal.IsInRole("Admin");
   if (isAdmin)
   {
       return new AdminRespository(resourceRepository);
   }
   else
   {
      return new UserRepository(principal.Identity, resourceRepository);
   }
}

我不想使用 AuthorizeAttribute 進行每個資源身份驗證的原因是不同的資源可能有不同的程式碼來檢查所有權,很難將程式碼集中在一個屬性中,並且它需要額外的數據庫操作,而這並不是真正必要的。另一個問題是,AuthroizeAttribute 發生在參數綁定之前,因此您需要確保操作的參數來自路由數據。否則,例如,從文章正文中,您將無法獲取參數值。

好簡單

要求

   public class PrivateProfileRequirement : IAuthorizationRequirement
   {
       public string ClaimType { get; }

       public PrivateProfileRequirement(string claimType)
       {
           ClaimType = claimType;
       }
   }

   public class PrivateProfileHandler : AuthorizationHandler<PrivateProfileRequirement>
   {
       protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PrivateProfileRequirement requirement)
       {
           if (context.User != null)
           {
               if (context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)))
               {
                   if (context.User.Identities.Any(i => string.Equals(i.GetId(), context.Resource)))
                   {
                       context.Succeed(requirement);
                   }
               }
           }

           return Task.CompletedTask;
       }
   }

啟動.cs

       services.AddAuthorization(options =>
       {
           options.AddPolicy("PrivateProfileRequirement",
               policy => policy
                        .RequireAuthenticatedUser()
                        .RequireRole(Role.Profile.ToRole())
                        .AddRequirements(new PrivateProfileRequirement(ClaimTypes.NameIdentifier)));
       });

控制器

public class ProfileController : Controller
{
   private readonly IAuthorizationService _authorizationService;

   public ProfileController(IAuthorizationService authorizationService)
   {
       _authorizationService = authorizationService;
   }
}

行動

public async Task<IActionResult> OnGetAsync(int id)
{
   var profile = _profileRepository.Find(id);

   if (profile == null)
   {
       return new NotFoundResult();
   }

   var authorizationResult = await _authorizationService
           .AuthorizeAsync(User, profile.Id, "PrivateProfileRequirement");

   if (authorizationResult.Succeeded)
   {
       return View();
   }

  return new ChallengeResult();     
}

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