Общие правила авторизации лучшие практики в MVC .NET

#asp.net-mvc #controller #authorization

#asp.net-mvc #контроллер #авторизация

Вопрос:

В настоящее время я сталкиваюсь с ситуацией, когда я украшаю свои методы действия контроллера HttpPost определенными атрибутами авторизации, а также указываю те же правила в моем HttpGet, чтобы гарантировать, что определенная функциональность недоступна в моем представлении.

Существует ли лучшая практика или лучшее решение, чтобы я мог указать фактические бизнес-правила в одном месте, чтобы включить DRY, а также не попасть в ситуацию взрыва кода, т. Е. иметь определенный атрибут авторизации для каждого правила..

Моя текущая ситуация выглядит следующим образом:

 public class MyController
{

   [HttpGet]
   public ActionResult List()
   {
       // This action is responsible for showing the list of records.  Each record
       // can potentially have a delete link but this is only shown for Administrators

       var viewModel = new MyViewModel()
       {
          CanDeleteRecordRole = Role.Administrator, // Duplicated rule here
          // other properties
       }
   }

   [HttpPost]
   [Authorize(Role.Administrator)]
   public ActionResult Delete(int id)
   {
      /// do stuff
   }
}

public class Role
{
   public const string Administrator = "Administrator";
}
  

Тогда, на мой взгляд, используя метод расширения, я бы использовал CanDeleteRecordRole, такой как:

 @if(Model.DisplayIfAuthorized(Model.CanDeleteRecordRole))
{
   <th>Delete record</th>
}

// and for td columns

@if(Model.DisplayIfAuthorized(Model.CanDeleteRecordRole))
{
   <td>My action link here for deletion</td>
}
  

Комментарии:

1. Не хочу быть педантичным, но я думаю, что в этом случае легко привести значимый аргумент, что защита, предоставляемая атрибутом для действия контроллера, не является выражением того же бизнес-правила, что и @if инструкции в разметке. В данном случае они применяются к одной и той же роли и находятся в той же близости в вашем коде (тот же V / C), но это действительно разные правила. Условие Model.CanDeleteRecordRole может применяться к роли, совершенно отличной от роли администратора (если вам это было нужно таким образом). Условие разметки намного более детализировано — это лучший способ сказать это.

2. Я думаю, что неправильно истолковал связь вашего показанного контроллера и представлений, но все же вы хотите сохранить то, что у вас есть. Если вы используете только ту часть, которая пропускает рендеринг ссылок удаления, у вас все равно будет открытое действие контроллера, которое может быть вызвано со стороны клиента за пределами вашей предполагаемой схемы авторизации. Это потребует определенных знаний и, вероятно, будет вредоносным, но отказ от АТРИБУТА во имя принципов DRY, вероятно, не лучший выбор здесь. HTH

3. Спасибо, Дэвид. Да, я не собираюсь отказываться от атрибута, а скорее от того, как я мог бы объединить требования к авторизации. Например. Если вдруг удалить, стать администратором другая роль, я бы предпочел изменить это в одном месте, а не помнить, что это нужно делать в нескольких и т.д…

4. Я не пробовал этот метод, но он определенно меня интересует. Я попробую это, когда у меня будет возможность. Это может быть близко к тому, что вы ищете: не выполняйте проверку подлинности на основе ролей, выполняйте проверку подлинности на основе действий

5. @DavidTansey Спасибо за ссылку. Мне придется переварить статью, но, возможно, есть что-то, что стоит изучить. Все может быть лучше, чем то, что я делаю сейчас, я думаю.

Ответ №1:

Да, определенно есть способ отделить бизнес-логику от логики авторизации и выполнить DRY. Эта область называется внешним управлением авторизацией (EAM) (см. определение Gartner).

Для достижения EAM вам нужно использовать больше, чем просто роли. Вам нужно использовать атрибуты, где атрибут по сути является просто парой ключ-значение, например citizenship=Canadian , clearance=SECRET , department=sales

Ролей недостаточно. Чтобы процитировать статью, ранее опубликованную в комментариях:

Что не так с проверками авторизации на основе ролей?

Было создано множество систем авторизации с проверками на основе ролей, так что же в них не так? Множество вещей, включая документацию и сопряжение, проблемы моделирования и инкапсуляции, а также рост и изменение требований.

Авторизация на основе ролей (также известная как управление доступом на основе ролей или RBAC) недостаточно гибка для выражения расширенных сценариев авторизации. Вам нужно обратиться к ABAC, модели управления доступом на основе атрибутов, определенной NIST.

С помощью ABAC вы можете легко реализовать такие правила, как:

  • пользователь может редактировать документ, которым он владеет
  • пользователь может просматривать все документы, принадлежащие одному и тому же отделу
  • пользователь с ролью рецензента может утвердить документ, если документ является черновиком и конфиденциальность документа равна или меньше, чем разрешение пользователя.

Нет никаких ограничений на то, что вы можете выразить в ABAC.

Стандартом де-факто и технологией, доступной для реализации ABAC, является XACML, расширяемый язык разметки управления доступом. XACML определяет:

  • архитектура авторизации с понятием
    • внешняя точка принятия политических решений (PDP), где принимаются решения
    • точка принудительного применения политики (PEP), которая защищает ваше приложение / код / API и обращается к PDP
    • точка информации о политике (PIP), используемая для извлечения дополнительных атрибутов и метаданных.
  • схема запроса / ответа: как задавать вопросы и получать ответы, например, Может ли Алиса просмотреть документ # 123?
  • богатый язык политик для реализации политик, подобных примерам, которые я приводил ранее.

В XACML все политики становятся централизованными в одном месте. Некоторые из преимуществ включают в себя: — более быстрое время разработки: вам больше не нужно писать код авторизации (if / else) в вашем приложении — улучшенная безопасность: вы можете использовать одни и те же политики во всех ваших приложениях, независимо от языка или технологии. Поэтому мой ответ не относится конкретно к .NET — улучшенные возможности аудита: если вы переместите свою логику аутентификации в центральную точку, основанную на политике, тогда их будет легче проверять — реализуйте принцип DRY.

Это всего лишь несколько доступных преимуществ.

Существует несколько решений с открытым исходным кодом и от поставщиков, таких как:

HTH, Дэвид.

Ответ №2:

Способ, которым я подходил к этой проблеме в прошлом, заключался в создании абстракции. Вместо ссылки на роль, которой разрешено что-то делать, обратитесь к строковой константе, которая содержит роли, которым разрешено что-то делать. Вот так:

 public class Role
{
    public const string DeleteRoles = "Administrator, role2, role3";
    ....
}
  

Затем создайте метод, который может интерпретировать эти строковые константы (я включил это в пользовательский принцип):

 public class CustomPrincipal
{
    ...
    public bool IsInRoles(string roles)
    {
        bool authorized = false;

        var roles = roles.Split(',');
        foreach (var role in roles)
        {
            if (this.CurrentPrincipal.IsInRole(role)
            {
                authorized = true;
                break;
            }
        }

        return authorized;
    }
    ...
}
  

Затем создайте пользовательский атрибут авторизации, который может использовать эти строковые константы с помощью метода IsInRoles():

 public class CustomAuthorizeAttribute: AuthorizeAttribute
{
    public string Roles { get; set; }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var isAuthorized = base.AuthorizeCore(httpContext);
        if (!isAuthorized)
        {
            return false;
        }

        isAuthorized = CustomPrincipal.Current.IsInRoles(this.Roles);

        return isAuthorized;
    }

}
  

которые вы используете в своих методах действий следующим образом:

 [HttpPost]
[CustomAuthorize(Roles = Role.DeleteRoles)]
public ActionResult Delete(int id)
{
    /// do stuff
}
  

Затем в представлении вы можете напрямую использовать метод IsInRoles():

 @if(CustomPrincipal.Current.IsInRoles(Role.DeleteRoles))
{
   <th>Delete record</th>
}
  

Вы можете реализовать это различными способами, но ключевым является абстракция.