Политики авторизации позволяют создавать более гибкую систему авторизации пользователей в ASP.NET Core MVC, позволяя использовать любые доступные утверждения (claims). На данный момент мы изучили возможности авторизации по ролям, а также, что из себя представляют утверждения и как ими управлять. В этой части мы рассмотрим применение политик авторизации.
Политика авторизации
Политика авторизации — это набор правил, которым должен соответствовать пользователь, чтобы получить доступ к ресурсу. При этом, в политику авторизации можно включать такие требования, как соответствие пользователя определенной роли.
Политики авторизации создаются в классе Program. В предыдущей части мы добавили для пользователя следующее утверждение:
Создадим следующую политику авторизации:
- Пользователь должен принадлежать к роли ADMIN
- У пользователя должно быть создано утверждение, представленное выше, то есть «Language = Russian».
Изменим класс Program следующим образом:
//настраиваем политику авторизации
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("OnlyRussianAdmin", policy =>
{
policy.RequireRole("ADMIN");
policy.RequireClaim("Language", "Russian");
});
});
Все настройки политик авторизации в ASP.NET Core осуществляются при добавлении в проект сервиса авторизации методом AddAuthorization(). В нашем примере options представляет собой объект типа AuthorizationOptions с помощью которого мы управляем политиками авторизации, используя следующие свойства и методы:
| Свойство | Default |
Возвращает или задает политику авторизации по умолчанию, которая будет использоваться, если атрибут Authorize задается без параметров |
| Метод | Add |
Добавляет новую политику с указанным именем, созданную на основе делегата . |
| Метод | Get |
Возвращает политику для указанного имени или значение NULL, если политика с этим именем не существует. |
В нашем примере, мы добавили новую политику методом Add у которого первый параметр метода — имя новой политики авторизации, а второй — делегат, в котором мы, используя AuthorizationPolicyBuilder настраиваем политику.
Класс AuthorizationPolicyBuilder содержит довольно много свойств и методов, позволяющих настраивать и объединять различные политики авторизации. Приведем основные из них:
Requirements |
Возвращает или задает список требований, которые должны выполняться чтобы пользователь был авторизован |
Add |
Добавляет новое требование в список Requirements. |
Require |
Пользователь должен быть аутентифицирован, чтобы чтобы соответствовать политике |
Require |
У пользователя должно быть определенное утверждение (Claim), чтобы соответствовать политике. При этом, значение Claim не проверяется — только наличие |
Require |
у пользователя должен быть определен Claim с типом type и одним из значений, перечисленных в values |
Require |
у пользователя должны быть определена хотя бы одна роль из списка values |
Require |
пользователь должен иметь определенное имя, чтобы соответствовать политике |
Таким образом, в нашей политике мы задали требование по наличию роли и определенного утверждения с заданным значением.
Чтобы применить новую политику авторизации мы также используем атрибут Authorize. Например,
[Authorize(Policy = "OnlyRussianAdmin")]
public IActionResult Privacy()
{
return View();
}
Теперь доступ у действию Privacy() будут иметь только те пользователи, которые соответствую заданной в параметрах атрибута политике. Не менее полезным, при определении политик авторизации, является метод Add и связанное с ним свойство Requirements .
Пользовательские требования к авторизации
Пользовательские требования к авторизации удобно применять там, где недостаточно использования только утверждений и ролей. Например, рассмотрим такой момент, как возраст пользователя. Часто (и даже на страницах этого блога), когда речь заходит о политике авторизации, в качестве примера для политики авторизации используется такое требование, как «пользователь должен быть старше/младше заданного возраста». И, далее, объекту пользователя добавляется некое утверждение (Claim) типа Age = 24 и используется в примере. Для примера такой подход вполне работоспособен, однако, в реальном приложении может вызвать серьезные проблемы. Поясним это, опять же, на примере.
Итак, пусть в нашем приложении есть ресурс доступ к которому строго запрещен пользователям младше 18 лет. В приложении регистрируется пользователь, которому на момент регистрации 17 лет и 11 месяцев. Мы устанавливаем объекту такого пользователя claim с его реальным возрастом, например, Age=17.9 и, тем самым, блокируем доступ к ресурсу. Проходит 2 месяца, пользователю уже явно больше 18 лет, а claim так и остался — 17.9 лет, либо нам необходимо будет как-то вручную отслеживать возраста всех пользователей и менять им утверждение Age, что тоже не логично.
В этом случае нам могут пригодится пользовательские требования (Requirements) к авторизации. Например, для проверки возраста мы можем при регистрации пользователя добавлять ему claim не фактического возраста, а день рождения и уже в пользовательском требовании проверять фактический возраст пользователя и давать либо запрещать доступ к ресурсу.
Продемонстрируем все вышесказанное на примере. Для этого, нам потребуется внести небольшое изменение в процедуру регистрации нового пользователя, а именно, в действие Register() контроллера AccountController:
[Route("{area}/{action}")]
[HttpPost]
public async Task<IActionResult> Register(RegisterModel model)
{
var user = model.GetUser();
IdentityResult result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
user = await _userManager.FindByEmailAsync(user.Email);
//добавляем дату рождения в Claimd польщователя
await _userManager.AddClaimAsync(user, new Claim("Birthday",model.Birthday.ToString("dd.MM.yyyy"),ClaimValueTypes.String));
return RedirectToAction("Index", "Home");
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
return View();
}
}
Теперь, при регистрации, пользователю сразу будет добавлено новое утверждение с датой его рождения. Создадим новое требование к авторизации пользователя. Для этого нам необходимо:
- Создать класс, реализующий интерфейс
IAuthorizationRequirement. Этот класс будет предоставлять механизм проверки того, прошел ли пользователь авторизацию или нет - Создать класс-наследник абстрактного класса
AuthorizationHandler, который будет производить необходимую проверку пользователя на соответствие требованию
Добавим в проект новую папку с названием CustomPolicy (папка может иметь любое название) и добавим в эту папку два файла. Первый файл назовем OlderThenPolicy.cs и класс в этой файле будет содержать максимально простой код:
using Microsoft.AspNetCore.Authorization;
namespace AspMvcAuth.CustomPolicy
{
public class OlderThenPolicy : IAuthorizationRequirement
{
public int Age { get; set; }
public OlderThenPolicy(int age)
{
Age = age;
}
}
}
По сути, это требование к пользователю. Здесь мы указываем минимальный возраст пользователя (18 лет). Второй файл назовем OlderThenHandler.cs и разместим в нем класс-обработчик нашего требования. Код класса будет следующий:
public class OlderThenHandler : AuthorizationHandler<OlderThenPolicy>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OlderThenPolicy requirement)
{
var claim = context.User.Claims.Where(claim => claim.Type == "Birthday").FirstOrDefault();
if (claim != null)
{
var date = DateTime.ParseExact(claim.Value, "dd.MM.yyyy", CultureInfo.InvariantCulture);
var age = DateTime.Now.Year - date.Year;
if (DateTime.Now.DayOfYear < date.DayOfYear)
age++;
if (age>=requirement.Age)
{
context.Succeed(requirement);
}
else
context.Fail();
}
else
context.Fail();
return Task.CompletedTask;
}
}
здесь в методе HandleRequirementAsync происходит проверка возраста пользователя. Для этого мы вначале пробуем получить утверждение, содержащее дату рождения пользователя:
var claim = context.User.Claims.Where(claim => claim.Type == "Birthday").FirstOrDefault();
Если у пользователя отсутствует это утверждение, то считаем, что проверка провалена и мы об этом сообщаем, вызвав метод:
context.Fail();
Если же дата рождения получена, то мы вычисляем возраст пользователя с точностью до целого числа лет:
var date = DateTime.ParseExact(claim.Value, "dd.MM.yyyy", CultureInfo.InvariantCulture); var age = DateTime.Now.Year - date.Year; if (DateTime.Now.DayOfYear < date.DayOfYear) age++;
и сравниваем полученное значение с заданным в требовании:
if (age>=requirement.Age)
если условие соблюдается, то проверка требования прошла успешно, о чем мы сообщаем вызвав метод
context.Succeed(requirement);
Чтобы добавить пользовательское требование к авторизации, изменим настройки сервиса авторизации следующим образом:
builder.Services.AddTransient<IAuthorizationHandler, OlderThenHandler>();
//настраиваем политику авторизации
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("OnlyRussianAdmin", policy =>
{
policy.RequireRole("ADMIN");
policy.RequireClaim("Language", "Russian");
policy.AddRequirements(new OlderThenPolicy(18)); //пользователь старше 18 лет
});
});
Для того, чтобы наше требование проверялось, мы зарегистрировали обработчик OlderThenHandler в качестве сервиса:
builder.Services.AddTransient<IAuthorizationHandler, OlderThenHandler>();
Так как мы уже применили эту политику авторизации в приложении, то можно проверить её действие с учётом нового требования. На данный момент у нас уже есть в базе один пользователь БЕЗ указания в Claims его даты рождения. Попробуем получить доступ к защищенному ресурсу:
Теперь зарегистрируем нового пользователя с датой рождения, например, 01.01.1990:
Войдем в приложение с новым логином/паролем
Добавим пользователя в группу ADMIN:
Добавим пользователь утверждение Language:
Теперь пользователь Alice полностью соответствует политике авторизации, а именно:
- Входит в роль ADMIN
- Содержит утверждение Language = Russian
- Имеет возраст старше 18 лет
Получим доступ к защищенному ресурсу:
Доступ получен, следовательно, политика авторизации работает.
Скачать код проекта из GithubИтого
Политики авторизации позволяют сделать более гибкую систему авторизации пользователей. Каждая политика может включать различные требования к пользователю, включая требования по ролям, наличие определенных утверждений, а также содержать пользовательские требования к авторизации.





