Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Теперь создадим простенькую инфраструктуру для авторизации и регистрации пользователя. Сначала добавим в наш проект вспомогательные модели, которые помогут выполнить логин и регистрацию пользователя. Для этого добавим в проект папку ViewModels. В нее добавим класс RegisterModel, который будет представлять модель регистрации:
using System.ComponentModel.DataAnnotations; namespace AuthApp.ViewModels { public class RegisterModel { [Required(ErrorMessage ="Не указан Email")] public string Email { get; set; } [Required(ErrorMessage = "Не указан пароль")] [DataType(DataType.Password)] public string Password { get; set; } [DataType(DataType.Password)] [Compare("Password", ErrorMessage = "Пароль введен неверно")] public string ConfirmPassword { get; set; } } }
В ней определено три свойства: Email, пароль и подтверждение пароля. Также во ViewModels добавим класс LoginModel, который будет представлять модель логина:
using System.ComponentModel.DataAnnotations; namespace AuthApp.ViewModels { public class LoginModel { [Required(ErrorMessage = "Не указан Email")] public string Email { get; set; } [Required(ErrorMessage = "Не указан пароль")] [DataType(DataType.Password)] public string Password { get; set; } } }
Затем добавим в папку Controllers новый контроллер, который назовем AccountController:
using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; using AuthApp.ViewModels; // пространство имен моделей RegisterModel и LoginModel using AuthApp.Models; // пространство имен UserContext и класса User using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; namespace AuthApp.Controllers { public class AccountController : Controller { private UserContext db; public AccountController(UserContext context) { db = context; } [HttpGet] public IActionResult Login() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginModel model) { if (ModelState.IsValid) { User user = await db.Users.FirstOrDefaultAsync(u => u.Email == model.Email && u.Password == model.Password); if (user != null) { await Authenticate(model.Email); // аутентификация return RedirectToAction("Index", "Home"); } ModelState.AddModelError("", "Некорректные логин и(или) пароль"); } return View(model); } [HttpGet] public IActionResult Register() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Register(RegisterModel model) { if (ModelState.IsValid) { User user = await db.Users.FirstOrDefaultAsync(u => u.Email == model.Email); if (user == null) { // добавляем пользователя в бд db.Users.Add(new User { Email = model.Email, Password = model.Password }); await db.SaveChangesAsync(); await Authenticate(model.Email); // аутентификация return RedirectToAction("Index", "Home"); } else ModelState.AddModelError("", "Некорректные логин и(или) пароль"); } return View(model); } private async Task Authenticate(string userName) { // создаем один claim var claims = new List<Claim> { new Claim(ClaimsIdentity.DefaultNameClaimType, userName) }; // создаем объект ClaimsIdentity ClaimsIdentity id = new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); // установка аутентификационных куки await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id)); } public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Login", "Account"); } } }
Так как в файле Startup ранее были добавлены сервисы Entity Framework, то мы можем получить объект контекста данных в конструкторе контроллера.
Для входа на сайт определена пара методов Login. Get-версия метода просто возвращает представление с формой, которые мы далее создадим. Post-версия принимает в качестве параметра модель LoginModel. Вначале смотрим, а есть ли с таким же email в базе данных какой-либо пользователь, если такой пользователь имеется в БД, то выполняем аутентификацию и устанавливаем аутентификационные куки. Чтобы не повторяться (в соответствии с принципом DRY), данный код вынесен в отдельный метод Authenticate:
private async Task Authenticate(string userName) { // создаем один claim var claims = new List<Claim> { new Claim(ClaimsIdentity.DefaultNameClaimType, userName) }; // создаем объект ClaimsIdentity ClaimsIdentity id = new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); // установка аутентификационных куки await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id)); }
Для установки кук применяется асинхронный метод контекста HttpContext.SignInAsync(). В качестве параметра он принимает
схему аутентификации, которая была использована при вызове метода services.AddAuthentication() в методе ConfigureServices()
в классе Startup. То есть
в нашем случае это CookieAuthenticationDefaults.AuthenticationScheme. А в качестве второго параметра передается объект ClaimsPrincipal, который представляет пользователя.
Для правильного создания и настройки объекта ClaimsPrincipal вначале создается список claims - набор данных, которые шифруются и добавляются в аутентификационные куки.
Каждый такой claim принимает тип и значение. В нашем случае у нас только один claim, который в качестве типа принимает константу ClaimsIdentity.DefaultNameClaimType
,
а в качестве значения - email пользователя.
Далее создается объект ClaimsIdentity, который нужен для инициализации ClaimsPrincipal. В ClaimsIdentity передается:
Ранее созданный список claims
Тип аутентификации, в данном случае "ApplicationCookie"
Тип данных в списке claims, который преставляет логин пользователя. То есть при добавлении claimа мы использовали в качестве типа
ClaimsIdentity.DefaultNameClaimType
, поэтому и тут нам надо указать то же самое значение. Мы, конечно, можем указать и разные значения, но тогда
система не сможет связать различные claim с логином пользователя.
Тип данных в списке claims, который представляет роль пользователя. Хотя у нас такого claim нет, который бы представлял роль пользователя, но
но опционально мы можем указать константу ClaimsIdentity.DefaultRoleClaimType
. В данном случае она ни на что не влияет.
И после вызова метода расширения HttpContext.SignInAsync
в ответ клиенту будут отправляться аутентификационные куки, которые при последующих
запросах будут передаваться обратно на сервер, десериализоваться и использоваться для аутентификации пользователя.
Метод Register
сделан аналогично методу Login, только теперь мы получаем данные регистрации через объект RegisterModel и перед аутентификацией
сохраняем эти данные в базу данных.
Для выхода из сайта определен метод Logout, суть которого в вызове метода
HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
В этот метод опять же передается название схемы аутентификации, использованное в классе Startup.
И после создания контроллера остается добавить нужные представления. Для этого в проекте в папке Views создадим подкаталог Account. И в него добавим представление Login.cshtml:
@model AuthApp.ViewModels.LoginModel <h2>Вход на сайт</h2> <a asp-action="Register" asp-controller="Account">Регистрация</a> <form asp-action="Login" asp-controller="Account" asp-anti-forgery="true"> <div class="validation" asp-validation-summary="ModelOnly"></div> <div> <div class="form-group"> <label asp-for="Email">Введите Email</label> <input type="text" asp-for="Email" /> <span asp-validation-for="Email" /> </div> <div class="form-group"> <label asp-for="Password">Введите пароль</label> <input asp-for="Password" /> <span asp-validation-for="Password" /> </div> <div class="form-group"> <input type="submit" value="Войти" class="btn btn-outline-dark" /> </div> </div> </form>
И также добавим представление для регистрации Register.cshtml:
@model AuthApp.ViewModels.RegisterModel <h2>Регистрация</h2> <form asp-action="Register" asp-controller="Account" asp-anti-forgery="true"> <div class="validation" asp-validation-summary="ModelOnly"></div> <div> <div> <label asp-for="Email">Введите Email</label><br /> <input type="text" asp-for="Email" /> <span asp-validation-for="Email" /> </div> <div> <label asp-for="Password">Введите пароль</label><br /> <input asp-for="Password" /> <span asp-validation-for="Password" /> </div> <div> <label asp-for="ConfirmPassword">Повторите пароль</label><br /> <input asp-for="ConfirmPassword" /> <span asp-validation-for="ConfirmPassword" /> </div> <div> <input type="submit" value="Регистрация" /> </div> </div> </form>
Для тестирования системы аутентификации изменим метод Index в контроллере HomeController, который имеется по умолчанию:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; namespace AuthApp.Controllers { public class HomeController : Controller { [Authorize] public IActionResult Index() { return Content(User.Identity.Name); } } }
Атрибут Authorize
предотвращает неаутентифицированный доступ к методу Index. Если анонимный пользователь попытается обратиться к этому методу,
то его перенаправит по пути Account/Login, то есть на метод логина. Если же пользователь аутентифицирован, то он увидит в браузере свой логин, который можно получить через свойство
User.Idenity.Name.
В итоге весь проект будет выглядеть следующим образом:
Теперь, когда все готово, протестируем приложение. При его запуске автоматически идет обращение по маршруту Home/Index, но так как мы неаутентифицированы, то нас перенаправляет на метод логина:
Естественно, что у нас пока нет никаких пользователей в БД и залогиниться мы не можем. Поэтому перейдем по ссылке регистрации и введем в регистрационную форму какие-нибудь данные:
И после нажатия на кнопку регистрации, данные пользователя будут добавлены в БД, произойдет аутентификация, и пользователя перенаправит на метод Index, который выведет его логин:
Кроме того, в самом браузере мы сможем увидеть сохраненные аутентификационные куки, которые называются .AspNetCore.Cookies
или .AspNetCore.[Название схемы аутентификации]
.
(Вторые куки - это куки для отслеживания antiforgery-токена, который используется для валидации запросов к приложению)
Таким образом, мы можем добавить простейшую систему аутентификации и авторизации в приложение ASP.NET Core