Аутентификация и авторизация в ASP.NET Core MVC. Работа с утверждениями (Claims)

С темой утверждений (Claims) в ASP.NET Core мы уже сталкивались, когда изучали «чистый» ASP.NET Core, правда эта тема прошла немного вскользь. Здесь мы попытаемся более детально разобраться с тем, что из себя представляют утверждения в ASP.NET Core Identity и как они используются в системе аутентификации и авторизации пользователей ASP.NET Core MVC.

Что такое «утверждение» (claim) в ASP.NET Core Identity

В ASP.NET Core Identity утверждения (claims) — это пары «ключ-значение» на основании которых пользователю даётся разрешение на выполнение каких-либо действий. К примеру, вы поступили в ВУЗ и вам выдан студенческий билет 1 сентября 2023 года. В этом случае, в терминах ASP.NET Core Identity мы могли бы определить следующее утверждение:

  1. Имя = студенческий билет
  2. Значение = 1 сентября 2023 года
  3. Кто выдал (эмитент) = название вашего ВУЗа

Это значит, что Вы вправе (получаете разрешение) заходить в корпуса конкретного учебного заведения (выполнять действие), предъявляя студенческий билет (утверждение), до тех пор, пока вы его не сдадите в отдел кадров (пока разрешение не отозвано эмитентом).

Вообще, даже роли пользователей, с которыми мы разбирались в предыдущей части, строго говоря, тоже представляют собой утверждения (claim), хоть и выделяются отдельно. Но здесь стоит отметить, что хоть каждая роль — это утверждение, однако, не каждое утверждение — это роль. Утверждение (claim) — более широкое понятие, используемое для аутентификации и авторизации пользователей.

Просмотр утверждений пользователя

Начнем с простого — просмотр уже имеющихся утверждений для пользователя. Для этого создадим в папке Areas/Account/Controllers новый контроллер с именем ClaimsController

и переопределим его действие Index следующим образом:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace AspMvcAuth.Areas.Account.Controllers
{
    [Authorize]
    public class ClaimsController : Controller
    {
        [Route("{area}/{controller}")]
        public IActionResult Index()
        {
            return View(User?.Claims);
        }
    }
}

Мы получаем утверждения пользователя через свойство User объекта HttpContext, который доступен в контроллере. Это свойство возвращает  объект ClaimsPrincipal для текущего пользователя, который содержит все утверждения. Здесь стоит отметить, что доступ к действиям контроллера будут иметь только аутентифицированные пользователи, о чем говорит атрибут [Authorize] контроллера. При вызове действия Index() мы будем возвращать в одноименное представление список утверждений для пользователя.

Теперь создадим необходимо представление. Для этого создадим новую папку Areas/Account/Views/Claims:

И разместим в этой папке новое представление Index.cshtml со следующим содержимым:

@model IEnumerable<System.Security.Claims.Claim>
@{
    ViewData["Title"] = "Утверждения";
}

<h2 class="bg-primary m-1 p-1 text-white">Утверждения</h2>

<table class="table table-sm table-bordered">
    <tr>
        <th>Кому принадлежит</th>
        <th>Эмитент</th>
        <th>Тип</th>
        <th>Значение</th>
    </tr>

    @foreach (var claim in Model.OrderBy(x => x.Type))
    {
        <tr>
            <td>@claim.Subject.Name</td>
            <td>@claim.Issuer</td>
            <td>@claim.Type</td>
            <td>@claim.Value</td>
        </tr>
    }
</table>

Это представление, следуя правилам маршрутизации, будет доступно по пути account/claims, добавим эту ссылку в шаблон _Layout.cshtml

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="Account" asp-controller="Claims" asp-action="Index">Утверждения</a>
</li>

Запустим приложение, авторизуемся и посмотрим на результат:

Как было сказано выше, роли также относятся к утверждениям пользователя, о чем свидетельствует и запись в таблице. Теперь мы можем создавать и удалять различные утверждения пользователя. Для этого, создадим ещё одно представление и назовем его Create.cshtml:

В этом представлении будет содержаться форма для создания нового утверждения для пользователя:

@{
    ViewData["Title"] = "Новое утверждение";
}

<h1 class="bg-info text-white">Новое утверждение</h1>
<a asp-area="Account" asp-controller="Claims" asp-action="Index" class="btn btn-secondary">Вернуться</a>

<div asp-validation-summary="All" class="text-danger"></div>

<form method="post">
    <div class="form-group">
        <label for="ClaimType">Тип:</label>
        <input name="ClaimType" class="form-control" />
    </div>
    <div class="form-group">
        <label for="ClaimValue">Значение:</label>
        <input name="ClaimValue" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Создать</button>
</form>

Теперь внесем небольшое изменение в предыдущее представление Index.cshtml добавив необходимые элементы управления утверждениями:

@model IEnumerable<System.Security.Claims.Claim>
@{
    ViewData["Title"] = "Утверждения";
}

<h2 class="bg-primary m-1 p-1 text-white">Утверждения</h2>

<a asp-area="Account" asp-controller="Claims" asp-action="Create" class="btn btn-secondary">Добавить утверждение</a>

<table class="table table-sm table-bordered">
    <tr>
        <th>Кому принадлежит</th>
        <th>Эмитент</th>
        <th>Тип</th>
        <th>Значение</th>
        <th>Удалить</th>
    </tr>

    @foreach (var claim in Model.OrderBy(x => x.Type))
    {
        <tr>
            <td>@claim.Subject.Name</td>
            <td>@claim.Issuer</td>
            <td>@claim.Type</td>
            <td>@claim.Value</td>
            <td>
                <form asp-action="Delete" method="post">
                    <input type="hidden" name="claimValues" value="@claim.Type;@claim.Value;@claim.Issuer" />
                    <button type="submit" class="btn btn-sm btn-danger">
                        Delete
                    </button>
                </form>
            </td>
        </tr>
    }
</table>

Здесь мы добавили ссылку»Добавить утверждение»:

<a asp-action="Create" class="btn btn-secondary">Добавить утверждение</a>

а также форму удаления утверждения

<form asp-action="Delete" method="post">
    <input type="hidden" name="claimValues" value="@claim.Type;@claim.Value;@claim.Issuer" />
    <button type="submit" class="btn btn-sm btn-danger">
        Delete
    </button>
</form>

При этом, обратите внимание та скрытый элемент input — в нем мы будем передавать тип, значение и эмитента утверждения для того, чтобы можно было произвести его удаление из хранилища. Теперь наше представление Index.cshtml будет выглядеть следующим образом:

Создадим в контроллере необходимые действия. Действие для загрузки представления Create.cshtml

[Route("{area}/{controller}/{action}")]
[HttpGet]
public IActionResult Create()
{
    return View();
}

Действие для создания нового утверждения:

[Route("{area}/{controller}/{action}")]
[HttpPost]
public async Task<IActionResult> Create([FromServices]UserManager<ApplicationUser> users, string claimType, string claimValue)
{
    var user = await users.GetUserAsync(HttpContext.User);
    Claim claim = new(claimType, claimValue, ClaimValueTypes.String);
    IdentityResult result = await users.AddClaimAsync(user, claim);

    if (result.Succeeded)
        return LocalRedirect("/account/claims");
    else
        return BadRequest(result);
}

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

IdentityResult result = await users.AddClaimAsync(user, claim);

Для удаления утверждения используется следующее действие:

[HttpPost]
[Route("{area}/{controller}/{action}")]
public async Task<IActionResult> Delete([FromServices] UserManager<ApplicationUser> users, string claimValues)
{
    var user = await users.GetUserAsync(HttpContext.User);

    string[] claimValuesArray = claimValues.Split(";");
    string claimType = claimValuesArray[0];
    string claimValue = claimValuesArray[1]; 
    string claimIssuer = claimValuesArray[2];

    Claim claim = User.Claims.Where(x => x.Type == claimType && x.Value == claimValue && x.Issuer == claimIssuer).FirstOrDefault();

    IdentityResult result = await users.RemoveClaimAsync(user, claim);

    if (result.Succeeded)
        return LocalRedirect("/account/claims");
    else
        return BadRequest(result);
}

Здесь мы по полученным сведениям об утверждении, ищем его в списке утверждений пользователя, используя LINQ:

Claim claim = User.Claims.Where(x => x.Type == claimType && x.Value == claimValue && x.Issuer == claimIssuer).FirstOrDefault();

и пытаемся его удалить, используя метод менеджера пользователей:

IdentityResult result = await users.RemoveClaimAsync(user, claim);

Теперь можно протестировать работу приложения с утверждениями пользователей.

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

Добавим новое утверждение для пользователя:

Новое утверждение появится в базе данных в таблице AspNetUserClaims:

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

Итого

В этой части мы научились работать с утверждениями пользователя — считывать список утверждений, создавать и удалять их. Как было сказано ранее, утверждения используются для авторизации пользователей в ASP.NET Core Identity, однако, чтобы использовать утверждения для этих целей мы также должны создать свои политики авторизации. О том, как работать с политиками авторизации мы узнаем в следующей части

Скачать код проекта из Github
Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии