Авторизация на основе политик в C# ASP.NET Core

Аутентификация и авторизация - это два термина, с которыми вы часто сталкиваетесь, читая о безопасности веб-приложений. В то время как первый используется для проверки учетных данных пользователя, второй используется для предоставления пользователю доступа к одному или нескольким ресурсам приложения. Существует два способа реализации авторизации в ASP.NET Core. К ним относятся авторизация на основе ролей и авторизация на основе политик. Ролевая авторизация использовалась в предыдущих версиях ASP.NET. Авторизация на основе политик была недавно введена в ASP.NET Core и предоставляет богатую, понятную и многократно используемую модель авторизации для защиты приложений, разработанных в ASP.NET Core. В этой статье обсуждается, как можно работать с авторизацией на основе политик в ASP.NET Core.

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

Вот сценарий с примером для объяснения. Предположим, вам поручено разработать структуру безопасности для приложения. Первоначально в приложении есть три роли: пользователь, администратор и менеджер. Теперь, если у вас есть несколько вариантов роли администратора, например CustomerAdmin, ReportsAdmin и SuperAdmin, вам придется учитывать каждый из них при разработке структуры безопасности. Вы также можете иметь несколько вариантов роли менеджера - ваша структура также должна учитывать их. Поскольку количество этих ролей значительно увеличивается, становится очень сложно эффективно справляться с этими ролями. Именно здесь пригодится модель авторизации на основе политик.

Использование авторизации на основе политик в ASP.NET Core

Модель безопасности на основе политик разъединяет авторизацию и логику приложения и предоставляет гибкую, повторно используемую и расширяемую модель безопасности в ASP.NET Core. Модель безопасности на основе политик основана на трех основных концепциях. К ним относятся политики, утверждения и обработчики. Политика состоит из нескольких утверждений. Утверждения, в свою очередь, содержат параметры данных для проверки личности пользователя. Наконец, обработчик используется для определения, имеет ли пользователь доступ к определенному ресурсу. Мы обсудим каждый из них более подробно в этом разделе - начнем с политики.

По сути, политика состоит из одного или нескольких утверждений и обычно регистрируется при запуске приложения в методе ConfigureServices() файла Startup.cs. Чтобы применить политики в ваших контроллерах или методах действий, вы можете воспользоваться атрибутом AuthorizeAttribute или фильтром AuthorizeFilter.

Вы можете создать экземпляр политики, используя класс AuthorizationPolicyBuilder, как показано в приведенном ниже фрагменте кода. Вы можете указать имена ролей, используя метод RequireRole.

var policy = new AuthorizationPolicyBuilder()
  .RequireAuthenticatedUser()
  .RequireRole("Admin")
  .Build();

Кроме того, вы можете создать экземпляр политики в методе ConfigureServices, как показано ниже.

services.AddMvc(obj =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
    obj.Filters.Add(new AuthorizeFilter(policy));
});

Регистрация политики

Простого определения политик недостаточно - вы также должны зарегистрировать политики, которые вы определили, с помощью middleware авторизации. Чтобы зарегистрировать политику, вы должны указать имя - это имя будет использоваться для ссылки на политику в контроллере или методы действия.

Ниже показано, как можно зарегистрировать политику при запуске приложения в ASP.NET Core.

using Microsoft.Extensions.DependencyInjection;
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("RequireManagerOnly", policy => 
               policy.RequireRole("Manager"));
    });
}

Вы также можете указать несколько разрешенных ролей при регистрации политики, как показано в коде ниже.

services.AddAuthorization(options =>
{
    options.AddPolicy("RequireManagerOnly", policy => 
          policy.RequireRole("Manager","Administrator"));
});

Применение политики

Как только политика будет зарегистрирована, вы можете применить политику в вашем контроллере или в действиях контроллера. Пример ниже показывает как применить политику на уровне контроллера.

[Authorize(Policy = "RequireAdminOnly")]
public class SecurityController: Controller
{
    //Action methods
}

Как видите, вместо указания ролей в атрибуте [Authorize] вы можете указать политику, которую вы хотите применить. Чтобы применить политику к методу действия, вы можете воспользоваться свойством Policy атрибута Authorize, как показано в коде ниже.

[Authorize(Policy = "RequireAdminOnly")]
public IActionResult DeleteAllSecureDocuments()
{
    //Some code
}

Вы также можете применить несколько политик к контроллеру или методу действия.

[Authorize(Policy = "ShouldBeEmployeeOnly")]
public class SecurityController : Controller
{
    [Authorize(Policy = "RequireAdminOnly")]
    public ActionResult DeleteUser()
    {
        //Your code here
    }
}

В приведенном выше примере были описани две политики авторизации - одна на уровне контроллера, а другая на уровне действий. Обратите внимение на действие DeleteUser в приведенном выше примере кода. Этот метод может быть выполнен только теми пользователями, которые удовлетворяют обеим политикам, а именно ShouldBeEmployeeOnly и Administrator. Другими словами, чтобы вызвать метод DeleteUser, удостоверение должно соответствовать двум политикам ShouldBeEmployeeOnly и Administrator.

Хотя авторизация на основе ролей легко реализуется в ASP.NET Core, она имеет ограниченную область действия. В качестве примера представьте, что вам нужно подтвердить пользователя на основе даты добавления или идентификатора. Вы не можете иметь роли для каждого из таких вариантов - это не совсем хорошее решение. Здесь вы можете воспользоваться преимуществами авторизации на основе утверждений - вы можете проверить личность пользователя на основе утверждений. Ниже описывается, как можно работать с авторизацией с помощью политик на основе утверждений.

Использование авторизации на основе политики через утверждения (Claims)

Авторизация на основе утверждений (утверждений) обеспечивает декларативный способ проверки доступа к ресурсам. При таком типе авторизации вы обычно проверяете значение утверждения, а затем предоставляете доступ к ресурсу на основе значения, содержащегося в утверждении. Утверждение - это пара ключ-значение, представляющая субъекта, то есть имя, возраст, номер паспорта, водительское удостоверение, паспорт, гражданство, дату рождения и т. Д. Таким образом, если dateofbirth - это имя утверждения, а значением утверждения будет дата рождения, Т.е. 1 января 1970 г.

Авторизация на основе утверждений может быть реализована с использованием политик. Приведенный ниже фрагмент кода показывает, как регистрируется простая политика утверждений. Политика ShouldBeOnlyEmployee ищет наличие утверждения EmployeeId. Политика добавляется в коллекцию служб с помощью метода AddPolicy.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("ShouldBeOnlyEmployee", policy => 
              policy.RequireClaim("EmployeeId"));
    });
}

Затем вы можете применить эту политику на уровне контроллера к атрибуту AuthorizeAttribute, как показано ниже.

[Authorize(Policy = "ShouldBeOnlyEmployee")]
public IActionResult SomeMethod()
{
    //Write your code here
}

Вы также можете иметь политики с несколькими утверждениями. Вы должны соответствующим образом зарегистрировать их в методе ConfigureServices класса Startup, как показано в ниже.

public void ConfigureServices(IServiceCollection services)  
{  
    services.AddMvc().SetCompatibilityVersion(
          CompatibilityVersion.Version_2_1);  
    services.AddAuthorization(options =>  
    {  
        options.AddPolicy("CustomSecurityPolicy", policy => 
             policy.RequireClaim("ShouldBeOnlyEmployee"));  
        options.AddPolicy("CustomSecurityPolicy", policy => 
             policy.RequireClaim("IsAdmin", "true"));  
    });  
}  

Требования

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

Требование включает в себя набор параметров. Эти параметры используются политикой для идентификации пользователя. Чтобы реализованить требование, вам нужно создать класс, который реализует интерфейс IAuthorizationRequirement. Следующий фрагмент кода иллюстрирует требование для политики MinimumExp.

public class MinimumExpRequirement : IAuthorizationRequirement
{
    public int MinimumExp { get; set; }
    public MinimumExpRequirement(int experience)
    {
        MinimumExp = experience;
    }    
}

Обработчики авторизации

Требование может иметь один или несколько обработчиков. Обработчик авторизации используется для оценки свойств требования. Чтобы создать обработчик авторизации, вы должны создать класс, который расширяет AuthorizationHandler<T> и реализует метод HandleRequirementAsync(). Следующий фрагмент кода показывает, как выглядит обработчик авторизации.

public class MinimumExpHandler : AuthorizationHandler<MinimumExpRequirement>
{
    protected override Task HandleRequirementAsync(
           AuthorizationHandlerContext context, 
           MinimumExpRequirement requirement)
    {
        throw new NotImplementedException();
    }
}

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

public class MinimumExpHandler : AuthorizationHandler<MinimumExpRequirement>
{
    protected override Task HandleRequirementAsync(
           AuthorizationHandlerContext context, 
           MinimumExpRequirement requirement)
    {
        var user = context.User;
        var claim = context.User.FindFirst("MinExperience");
        if(claim != null)
        {
            var expInYears = int.Parse(claim?.Value);
            if (expInYears >= requirement.MinimumExp)
                context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

Несколько обработчиков для одного требования

Требование также может иметь несколько обработчиков. Возможно, вы захотите использовать несколько обработчиков для требования, когда вам нужно оценить требование на основе нескольких условий. Например, вы можете проверить, является ли пользователь сотрудником и возраст работника превышает 50 лет. Таким образом, для каждого из этих условий вам нужен отдельный обработчик.

public class EmployeeRequirement : IAuthorizationRequirement
{
   //Write your code here
}
public class EmployeeRoleHandler : 
         AuthorizationHandler<EmployeeRequirement>
{
    //Write your code here to check if the user is an employee
}
public class MinimumAgeHandler : 
         AuthorizationHandler<EmployeeRequirement>
{
    //Write your code here to validate min age
}

Регистрация обработчика

Вы должны зарегистрировать обработчики в коллекции сервисов. Чтобы зарегистрировать обработчик, созданный ранее в этой статье, вы должны написать следующий код в методе ConfigureServices класса Startup.

public void ConfigureServices(IServiceCollection services)
{
    //Other code
    services.AddSingleton<IAuthorizationHandler, 
    MinimumExpHandler>();
}

Обратите внимание на использование метода AddSingleton в методе ConfigureServices, приведенном выше. При работе с внедрением зависимостей в ASP.NET Core вы можете указать время жизни службы, используя методы AddTransient, AddScoped или AddSingleton. В этом примере используется время жизни синглтона. При использовании этого типа срока службы службы экземпляр службы будет создан при первом запросе. Последующие запросы к службе будут повторно использовать тот же экземпляр.

public void ConfigureServices(IServiceCollection services)
{      services.AddMvc().SetCompatibilityVersion(
       CompatibilityVersion.Version_2_1);
    services.AddAuthorization(options =>
    {
        options.AddPolicy(
            "MinExperience", policy =>
            policy.Requirements.Add(
                  new MinimumExpRequirement(5)));
    });
    services.AddSingleton<IAuthorizationHandler, 
           MinimumExpHandler>();
}

Резюме

Модель авторизации в ASP.NET Core была существенно обновлена с введением простой декларативной модели авторизации на основе политик. Авторизация на основе политик помогает вам построить слабо связанную модель безопасности, отделив логику авторизации и приложения.