Аутентификация на основе куки. Часть 2

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 13.12.2019

Теперь создадим простенькую инфраструктуру для авторизации и регистрации пользователя. Сначала добавим в наш проект вспомогательные модели, которые помогут выполнить логин и регистрацию пользователя. Для этого добавим в проект папку 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.

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

Аутентификация в ASP.NET Core 2.0

Теперь, когда все готово, протестируем приложение. При его запуске автоматически идет обращение по маршруту Home/Index, но так как мы неаутентифицированы, то нас перенаправляет на метод логина:

Логин в ASP.NET Core

Естественно, что у нас пока нет никаких пользователей в БД и залогиниться мы не можем. Поэтому перейдем по ссылке регистрации и введем в регистрационную форму какие-нибудь данные:

Регистрация в ASP.NET Core

И после нажатия на кнопку регистрации, данные пользователя будут добавлены в БД, произойдет аутентификация, и пользователя перенаправит на метод Index, который выведет его логин:

Авторизация в ASP.NET Core

Кроме того, в самом браузере мы сможем увидеть сохраненные аутентификационные куки, которые называются .AspNetCore.Cookies или .AspNetCore.[Название схемы аутентификации]. (Вторые куки - это куки для отслеживания antiforgery-токена, который используется для валидации запросов к приложению)

Таким образом, мы можем добавить простейшую систему аутентификации и авторизации в приложение ASP.NET Core

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850