Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Хотя встроенный функционал по созданию политик авторизации покрывает множество случаев для их определения, но он имеет ограниченные возможности. В частности, в прошлом проекте класс User имел свойство Year, указывающее на год рождения пользователя:
public class User { public int Id { get; set; } public string Email { get; set; } public string Password { get; set; } public string City { get; set; } public string Company { get; set; } // год рождения пользователя public int Year { get; set; } }
Что если мы хотим ограничить доступ в зависимости от возраста пользователя. Для этого нам надо создать собственное ограничение.
Возьмем проект из прошлой темы и добавим в него класс ограничения, который назовем AgeRequirement:
using Microsoft.AspNetCore.Authorization; public class AgeRequirement : IAuthorizationRequirement { protected internal int Age { get; set; } public AgeRequirement(int age) { Age = age; } }
Класс ограничения должен реализовать интерфейс IAuthorizationRequirement. С помощью свойства Age устанавливается минимально допустимый возраст.
Сам класс ограничения только устанавливает некоторые лимиты, больше он ничего не делает. Чтобы его использовать при обработке запроса, нам надо добавить специальный класс - обработчик. Итак, добавим в проект новый класс AgeHandler:
using System; using System.Threading.Tasks; using System.Security.Claims; using Microsoft.AspNetCore.Authorization; public class AgeHandler : AuthorizationHandler<AgeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AgeRequirement requirement) { if (context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth)) { var year = 0; if(Int32.TryParse(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value, out year)) { if ((DateTime.Now.Year - year) >= requirement.Age) { context.Succeed(requirement); } } } return Task.CompletedTask; } }
Класс обработчика должен наследоваться от класса AuthorizationHandler<T>, где параметр T
представляет тип
ограничения. Вся обработка производится в методе HandleRequirementAsync()
. Этот метод вызывается системой авторизации при доступе к ресурсу,
к которому применяется ограничение, используемое обработчиком.
В качестве параметов метод HandleRequirementAsync()
получает объект применяемого ограничения и контекст авторизации AuthorizationHandlerContext,
который содержит информацию о запросе. В частности, через свойство User
он возвращает объект ClaimPrincipal, представляющий текущего пользователя.
А методы класса AuthorizationHandlerContext позволяют управлять авторизацией. Так, метод Succeed(requirement)
вызывается, если запрос соответствует ограничению requirement.
И наоброт, метод Fail()
, если запрос не соответствует ограничению.
В данном случае мы получаем для текущего пользователя claim с типом ClaimTypes.DateOfBirth
. Предполагается, что этот claim содержит год рождения пользователя.
И далее по этому году получаем возраст пользователя относительно текущей даты. И если возраст оказался больше минимально допустимого, то вызываем
метод context.Succeed(requirement)
. Вызов этого метода будет означать, что работа обработчика завершилась успешно. Если этот метод не вызывается,
то считается, что авторизация прошла неудачно.
В итоге проект будет выглядеть следующим образом:
Далее изменим класс Startup, чтобы применить эти классы:
using ClaimsApp.Infrastructure; using ClaimsApp.Models; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace ClaimsApp { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { string connection = "Server=(localdb)\mssqllocaldb;Database=claimsstoredb;Trusted_Connection=True;"; services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connection)); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = new Microsoft.AspNetCore.Http.PathString("/Account/Register"); }); // встраиваем сервис AgeHandler services.AddTransient<IAuthorizationHandler, AgeHandler>(); services.AddAuthorization(opts => { // устанавливаем ограничение по возрасту opts.AddPolicy("AgeLimit", policy => policy.Requirements.Add(new AgeRequirement(18))); }); services.AddControllersWithViews(); } public void Configure(IApplicationBuilder app) { app.UseDeveloperExceptionPage(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } }
Здесь надо отметить два момента. Во-первых, в методе ConfigureServices()
происходит установка зависимости для IAuthorizationHandler:
services.AddTransient<IAuthorizationHandler, AgeHandler>();
Во-вторых, в коллекцию Requirements
добавляется кастомное ограничение:
opts.AddPolicy("AgeLimit", policy => policy.Requirements.Add(new AgeRequirement(18)));
Теперь нам остается установить нужный объект Claim. Для этого можно изменить в контроллере AccountController метод Authenticate, который вызывается при регистрации:
private async Task Authenticate(User user) { var claims = new List<Claim> { new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email), new Claim(ClaimTypes.Locality, user.City), new Claim("company", user.Company), new Claim(ClaimTypes.DateOfBirth, user.Year.ToString()) }; ClaimsIdentity id = new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id)); }
Полный код контроллера AccountController в итоге должен выглядеть так:
using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using ClaimsApp.Models; using Microsoft.EntityFrameworkCore; using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; namespace ClaimsApp.Controllers { public class AccountController : Controller { private ApplicationContext _context; public AccountController(ApplicationContext context) { _context = context; } [HttpGet] public IActionResult Register() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Register(RegisterModel model) { if (ModelState.IsValid) { User user = await _context.Users.FirstOrDefaultAsync(u => u.Email == model.Email); if (user == null) { user = new User { Email = model.Email, Password = model.Password, Year = model.Year, City = model.City, Company = model.Company }; _context.Users.Add(user); await _context.SaveChangesAsync(); await Authenticate(user); return RedirectToAction("Index", "Home"); } else ModelState.AddModelError("", "Некорректные логин и(или) пароль"); } return View(model); } private async Task Authenticate(User user) { var claims = new List<Claim> { new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email), new Claim(ClaimTypes.Locality, user.City), new Claim("company", user.Company), new Claim(ClaimTypes.DateOfBirth, user.Year.ToString()) }; ClaimsIdentity id = new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id)); } } }
И далее мы можем использовать созданную политику для ограничения доступа:
public class HomeController : Controller { [Authorize(Policy = "AgeLimit")] public IActionResult Index() { return View(); } public IActionResult About() { return Content("For all ages"); } }
Теперь к методу Index смогут обратиться только те, кто удовлетворяет ограничению AgeLimit (в данном случае кому исполнилось 18 лет), в то время как к методу About смогут обратиться все желающие.