ClaimPrincipal и объекты Claim

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

В прошлой теме был рассмотрен объект HttpContext.User, который представляет класс ClaimsPrincipal и который хранит данные пользователя в свойстве Identity в виде объекта ClaimsIdentity. Но что представляют сами данные пользователя? Для хранения различных данных пользователя во фреймворке ASP.NET Core определяются объекты claim.

Объекты claim представляют некоторую информацию о пользователе, которую мы можем использовать для авторизации в приложении. Например, у пользователя может быть определенный возраст, город, страна проживания, любимая музыкальная группа и прочие признаки. И все эти признаки могут представлять отдельные объекты claim. И в зависимости от значения этих claim мы можем предоставлять пользователю доступ к тому или иному ресурсу. Таким образом, claims представляют более общий механизм авторизации нежели стандартные логины или роли, которые привязаны лишь к одному определенному признаку пользователя.

Каждый объект claim представляет класс Claim из пространства имен System.Security.Claims, который определяет следующие свойства:

  • Issuer: "издатель" или название системы, которая выдала данный claim

  • Subject: возвращает информацию о пользователе в виде объекта ClaimsIdentity

  • Type: возвращает тип объекта claim

  • Value: возвращает значение объекта claim

Создание Claim

Для создания объекта Claim определено множество конструкторов, но чаще всего применяется следующая версия конструктора:

public Claim(string type, string value)

В качестве первого параметра в конструктор передается тип claima - это некоторая строка, которая, как правило, описывает назначение claima. В качестве второго параметра передается значение этого claima. Например, простейшее создание claima:

var usernameClaim = new Claim(ClaimTypes.Name, "Tom");

В качестве типов можно использовать встроенные константы, типа ClaimTypes.Name, которая имеет значение "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" и которая обычно применяется для установки имени пользователя (то, что потом мы сможем получить через свойство HttpContext.User.Identity.Name). И в данном случае этот claim будет иметь значение "Tom".

Все объекты claim, которые описывают пользователя, затем можно передать в виде коллекции в конструктор ClaimsIdentity:

var usernameClaim = new Claim(ClaimTypes.Name, "Tom");
var claims = new List<Claim> { usernameClaim };
var claimsIdentity = new ClaimsIdentity(claims, "Cookies");

Рассмотрим на небольшом примере:

using Microsoft.AspNetCore.Authentication.Cookies;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie();

var app = builder.Build();

app.UseAuthentication();

app.MapGet("/login/{username}", async (string username, HttpContext context) =>
{
    var claims = new List<Claim> { new (ClaimTypes.Name, username) };
    var claimsIdentity = new ClaimsIdentity(claims, "Cookies");
    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    await context.SignInAsync(claimsPrincipal);
    return $"Установлено имя {username}";
});
app.Map("/", (HttpContext context) =>
{
    var user = context.User.Identity;
    if (user is not null && user.IsAuthenticated) 
        return $"UserName: {user.Name}";
    else return "Пользователь не аутентифицирован.";
});
app.MapGet("/logout", async (HttpContext context) =>
{
    await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return "Данные удалены";
});

app.Run();

В данном примере в конечной точке app.MapGet("/login/{username}") через параметр "username" получаем некоторое условное имя пользователя, создаем из него Claim, передаем его в ClaimsIdentity. В итоге после этот claim будет сохранен в аутентификационных куках. И когда в следующем запросе приложение получит эти аутентификационные куки, оно сможет извлечь эти данные и использовать их при создании объекта ClaimsPrincipal.

Установка Claim и HttpContext.User.Identity.Name в ASP.NET Core и C#

Следует отметить, что по умолчанию значение claim с типом ClaimTypes.Name (либо ClaimsIdentity.DefaultNameClaimType) передается свойству HttpContext.User.Identity.Name.

Управление объектами Claim

Для работы с объектами Claim в классе ClaimsPrincipal есть следующие свойства и методы:

  • Claims: свойство, которое возвращает набор ассоциированных с пользователем объектов claim

  • FindAll(type) / FindAll(predicate): возвращает все объекты claim, которые соответствуют определенному типу или условию

  • FindFirst(type) / FindFirst(predicate): возвращает первый объект claim, который соответствуют определенному типу или условию

  • HasClaim(type, value) / HasClaim(predicate): возвращает значение true, если пользователь имеет claim определенного типа с определенным значением

  • IsInRole(name): возвращает значение true, если пользователь принадлежит роли с названием name

С помощью объекта ClaimsIdentity, который возвращается свойством User.Identity, мы можем управлять объектами claim у текущего пользователя. В частности, класс ClaimsIdentity определяет следующие свойства и методы:

  • Claims: свойство, которое возвращает набор ассоциированных с пользователем объектов claim

  • AddClaim(claim): добавляет для пользователя объект claim

  • AddClaims(claims): добавляет набор объектов claim

  • FindAll(type) / FindAll(predicate): возвращает все объекты claim, которые соответствуют определенному типу или условию

  • FindFirst(type) / FindFirst(predicate): возвращает первый объект claim, который соответствуют определенному типу или условию

  • HasClaim(predicate): возвращает значение true, если пользователь имеет claim, соответствующий определенному условию

  • RemoveClaim(claim): удаляет объект claim

  • TryRemoveClaim(claim): удаляет объект claim и возвращает true при успешном удалении

Например, определим у пользователя несколько объектов claim:

using Microsoft.AspNetCore.Authentication.Cookies;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie();

var app = builder.Build();

app.UseAuthentication();

app.MapGet("/login", async (HttpContext context) =>
{
    var username = "Tom";
    var company = "Microsoft";
    var phone = "+12345678901";

    var claims = new List<Claim> 
    { 
        new Claim (ClaimTypes.Name, username),
        new Claim ("company", company),
        new Claim(ClaimTypes.MobilePhone,phone)
    };
    var claimsIdentity = new ClaimsIdentity(claims, "Cookies");
    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    await context.SignInAsync(claimsPrincipal);
    return Results.Redirect("/");
});
app.Map("/", (HttpContext context) =>
{
    // аналогично var username = context.User.Identity.Name
    var username = context.User.FindFirst(ClaimTypes.Name);
    var phone = context.User.FindFirst(ClaimTypes.MobilePhone);
    var company = context.User.FindFirst("company");
    return $"Name: {username?.Value}\nPhone: {phone?.Value}\nCompany: {company?.Value}";
});
app.MapGet("/logout", async (HttpContext context) =>
{
    await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return "Данные удалены";
});

app.Run();

В конечной точке app.MapGet("/login") в куки сохраняются объект ClaimsPrincipal с тремя объектами claims. Они представляют имя, компанию и телефон пользователя. Причем для добавления некоторых claim мы можем воспользоваться встроенными типами, как для имени или телефона пользователя:

new Claim(ClaimTypes.MobilePhone,phone)

Для других данных мы можем определить свои типы, просто передав какую-нибудь строку, как в случае с компанией:

new Claim ("company", company),

Затем в приложении при обработке запроса мы можем получить эти данные. Как в примере выше в конечной точке app.Map("/"):

var phone = context.User.FindFirst(ClaimTypes.MobilePhone);
var company = context.User.FindFirst("company");

В метод FindFirst передается тип claim. Стоит учитывать, что этот метод возвращает объект Claim?, то есть результатом метода может быть значение null (например, если пользователь не аутентифицирован или объект claim не установлен). Соответственно при обращении к значению claim необходимо проверять его на null.

Таким образом, после обращения по пути "/login" в куках будут сохранены данные пользователя:

установка и получение Claim у ClaimsPrincipal в ASP.NET Core и C#

Если мы динамически решим добавить новый claim или удалить существующий, то после изменения claim необходимо заново пересоздавать объект ClaimsPrincipal и перезаписывать аутентификационные куки или jwt-токен, где эти данные храняться.

using Microsoft.AspNetCore.Authentication.Cookies;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie();

var app = builder.Build();

app.UseAuthentication();
// Добавление возраста
app.MapGet("/addage", async (HttpContext context) =>
{
    if(context.User.Identity is ClaimsIdentity claimsIdentity)
    {
        claimsIdentity.AddClaim(new Claim("age", "37"));
        var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
        await context.SignInAsync(claimsPrincipal);
    }
    return Results.Redirect("/");
});
// удаление телефона
app.MapGet("/removephone", async (HttpContext context) =>
{
    if (context.User.Identity is ClaimsIdentity claimsIdentity)
    {
        var phoneClaim = claimsIdentity.FindFirst(ClaimTypes.MobilePhone);
        // если claim успешно удален
        if(claimsIdentity.TryRemoveClaim(phoneClaim))
        {
            var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
            await context.SignInAsync(claimsPrincipal);
        }
    }
    return Results.Redirect("/");
});
app.MapGet("/login", async (HttpContext context) =>
{
    var username = "Tom";
    var company = "Microsoft";
    var phone = "+12345678901";

    var claims = new List<Claim> 
    { 
        new Claim (ClaimTypes.Name, username),
        new Claim ("company", company),
        new Claim(ClaimTypes.MobilePhone,phone)
    };
    var claimsIdentity = new ClaimsIdentity(claims, "Cookies");
    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    await context.SignInAsync(claimsPrincipal);
    return Results.Redirect("/");
});
app.Map("/", (HttpContext context) =>
{
    var username = context.User.FindFirst(ClaimTypes.Name);
    var phone = context.User.FindFirst(ClaimTypes.MobilePhone);
    var company = context.User.FindFirst("company");
    var age = context.User.FindFirst("age");
    return $"Name: {username?.Value}\nPhone: {phone?.Value}\n" +
    $"Company: {company?.Value}\nAge: {age?.Value}";
});
app.MapGet("/logout", async (HttpContext context) =>
{
    await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return "Данные удалены";
});

app.Run();

В данном случае конечная точка app.MapGet("/removephone") удаляет телефон, а app.MapGet("/addage") добавляет возвраст в claims

Если нам надо сохранить набор значений, то все они передаются по одному типу. Затем с помощью метода FindAll() можно получить список этих значений. Например, сохраним для пользователя набор иностранных языков:

using Microsoft.AspNetCore.Authentication.Cookies;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie();

var app = builder.Build();

app.UseAuthentication();

app.MapGet("/login", async (HttpContext context) =>
{
    var claims = new List<Claim> 
    { 
        new Claim (ClaimTypes.Name, "Tom"),
        new Claim ("languages", "English"),
        new Claim ("languages", "German"),
        new Claim ("languages", "Spanish")
    };
    var claimsIdentity = new ClaimsIdentity(claims, "Cookies");
    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    await context.SignInAsync(claimsPrincipal);
    return Results.Redirect("/");
});
app.Map("/", (HttpContext context) =>
{
    var username = context.User.FindFirst(ClaimTypes.Name);
    var languages = context.User.FindAll("languages");
    // объединяем список claims в строку
    var languagesToString = "";
    foreach (var l in languages)
        languagesToString = $"{languagesToString} {l.Value}";    
    return $"Name: {username?.Value}\nLanguages: {languagesToString}";
});
app.MapGet("/logout", async (HttpContext context) =>
{
    await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return "Данные удалены";
});

app.Run();
сохранение в Claim массивов в ASP.NET Core и C#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850