Авторизация на основе утверждений в ASP.NET Core

При создании удостоверения для пользователя приложения при входе в приложение поставщик удостоверений может назначить одно или несколько утверждений удостоверению пользователя. Утверждение — это пара «имя-значение», представляющая то, что собой представляет субъект (пользователь, приложение, служба, устройство или компьютер), а не то, что субъект может сделать. Утверждение можно оценить приложением, чтобы определить права доступа к данным и другим защищенным ресурсам во время авторизации, а также использовать для принятия или выражения решений проверки подлинности о субъекте. Удостоверение может содержать несколько утверждений с несколькими значениями и содержать несколько утверждений одного типа. В этой статье объясняется, как добавить проверки утверждений для авторизации в приложении ASP.NET Core.

В этой статье используются Razor примеры компонентов и рассматриваются Blazor сценарии авторизации. Дополнительные Blazor рекомендации см. в разделе "Дополнительные ресурсы ". Инструкции по Pages и MVC можно найти в следующих ресурсах:

  • авторизация на основе утверждений в ASP.NET Core Страницы
  • Авторизация на основе утверждений в ASP.NET Core MVC

Пример приложения

Пример для этой статьи — это приложение ( репозиторий GitHub) (как скачать). В примере приложения используются заполненные учетные записи с предварительно настроенными утверждениями для демонстрации большинства примеров в этой статье. Дополнительные сведения см. в файле README примера (README.md).

Предостережение

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

Добавление проверок утверждений

Проверки авторизации на основе утверждений:

Компонент AuthorizeView (AuthorizeView компонент в Blazor документации) поддерживает авторизациюна основе политик , где для политики требуется одно или несколько утверждений. Кроме того, авторизация на основе утверждений с помощью одной или нескольких проверок политики может быть настроена с помощью [Authorize] атрибутов в Razor компонентах. Разработчик должен создать и зарегистрировать политику, выражающую требования к утверждениям. В этом разделе рассматриваются основные понятия. Полный охват см. в разделе ASP.NET Core Blazor аутентификации и авторизации.

Простейший тип политики заявок ищет наличие заявки и не проверяет значение.

Регистрация политики выполняется в рамках конфигурации службы авторизации в файле приложения Program :

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));

Замечание

WebApplicationBuilder.ConfigureApplication (источник reference) автоматически добавляет вызов UseAuthorization при регистрации IAuthorizationHandlerProvider, что было поведением для ASP.NET Core с момента выпуска .NET 8. Таким образом, вызов UseAuthorization явно для серверных приложений Blazor в .NET 8 или более поздней версии технически избыточен, но не является вредным. Вызов его в коде разработчика после того, как он уже был вызван платформой просто без операций.

Замечание

Ссылки на справочную документацию .NET обычно открывают основную ветку репозитория, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для конкретного релиза, используйте раскрывающийся список Переключение ветвей или тегов. Дополнительные сведения см. в разделе Как выбрать тег версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Регистрация политики выполняется в рамках конфигурации службы авторизации в файле приложения Program :

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));

В Blazor Server приложениях вызовите UseAuthorization после строки, которая вызывает UseAuthentication (если такая имеется):

app.UseAuthentication(); // Only present if not called internally
app.UseAuthorization();

Регистрация политики выполняется в рамках конфигурации службы авторизации (Startup.ConfigureServicesStartup.cs):

services.AddAuthorization(options =>
{
    options.AddPolicy("EmployeeOnly", policy =>
        policy.RequireClaim("EmployeeNumber"));
});

В приложениях Blazor Server вызовите UseAuthorization внутри Startup.Configure после строки, которая вызывает UseAuthentication (если она есть).

app.UseAuthentication(); // Only present if not called internally
app.UseAuthorization();

Blazor WebAssembly приложения вызывают AddAuthorizationCore в Program файле, чтобы добавить службы авторизации:

builder.Services.AddAuthorizationCore();

Примените политику с помощью Policy свойства атрибута[Authorize], чтобы указать имя политики. В следующем примере EmployeeOnly политика проверяет наличие EmployeeNumber утверждения в текущем удостоверении:

Для авторизации на основе политик с помощью AuthorizeView компонента используйте AuthorizeView.Policy параметр с одним именем политики.

Pages/PassEmployeeOnlyPolicyWithAuthorizeView.razor:

@page "/pass-employeeonly-policy-with-authorizeview"

<h1>Pass 'EmployeeOnly' policy with AuthorizeView</h1>

<AuthorizeView Policy="EmployeeOnly">
    <Authorized>
        <p>You satisfy the 'EmployeeOnly' policy.</p>
    </Authorized>
    <NotAuthorized>
        <p>You <b>don't</b> satisfy the 'EmployeeOnly' policy.</p>
    </NotAuthorized>
</AuthorizeView>

Кроме того, примените политику, используя свойство Policy на атрибуте [Authorize], чтобы указать имя политики. В следующем примере EmployeeOnly политика проверяет наличие EmployeeNumber утверждения в текущем удостоверении:

Pages/PassEmployeeOnlyPolicyWithAuthorizeAttribute.razor:

@page "/pass-employeeonly-policy-with-authorize-attribute"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "EmployeeOnly")]

<h1>Pass 'EmployeeOnly' policy with [Authorize] attribute</h1>

<p>You satisfy the 'EmployeeOnly' policy.</p>

При создании политики можно указать список разрешенных значений. Следующая политика проходит только для сотрудников, число сотрудников которых равно 1, 2, 3, 4 или 5:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("Founders", policy =>
        policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Founders", policy =>
                      policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
services.AddAuthorization(options =>
{
    options.AddPolicy("Founder", policy =>
        policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});

Pages/PassFounderPolicyWithAuthorizeView.razor:

@page "/pass-founder-policy-with-authorizeview"

<h1>Pass 'Founder' policy with AuthorizeView</h1>

<AuthorizeView Policy="Founder">
    <Authorized>
        <p>You satisfy the 'Founder' policy.</p>
    </Authorized>
    <NotAuthorized>
        <p>You <b>don't</b> satisfy the 'Founder' policy.</p>
    </NotAuthorized>
</AuthorizeView>

Добавьте универсальную проверку утверждений

Если значение заявки не является единичным или вам требуется более гибкая логика оценки заявок, например сопоставление шаблонов, проверка выдающего заявки или синтаксический анализ сложных значений заявок, используйте RequireAssertion вместе с HasClaim. Например, для следующей политики требуется, чтобы утверждение пользователя email заканчивалось определенным доменом:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
});
services.AddAuthorization(options =>
{
    options.AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
});

Дополнительные сведения см. в разделе Авторизация на основе политик в ASP.NET Core.

Оценка нескольких политик

Несколько политик применяются через несколько AuthorizeView компонентов. Внутренний компонент требует, чтобы пользователь предоставил свою политику и политику всех родительских AuthorizeView компонентов.

Следующий пример:

  • Требуется политика CustomerServiceMember, указывающая, что пользователь находится в отделе обслуживания клиентов организации, так как имеет требование Department со значением Customer Service.
  • Кроме того, требуется HumanResourcesMember политика, указывающая, что пользователь находится в отделе кадров организации, так как он имеет Department претензию со значением Human Resources.

В файле Program приложения:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("CustomerServiceMember", policy =>
        policy.RequireClaim("Department", "Customer Service"))
    .AddPolicy("HumanResourcesMember", policy =>
        policy.RequireClaim("Department", "Human Resources"));

В файле Program приложения:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomerServiceMember", policy =>
        policy.RequireClaim("Department", "Customer Service"));
    options.AddPolicy("HumanResourcesMember", policy =>
        policy.RequireClaim("Department", "Human Resources"));
});

В Startup.ConfigureServices (Startup.cs):

services.AddAuthorization(options =>
{
    options.AddPolicy("CustomerServiceMember", policy =>
        policy.RequireClaim("Department", "Customer Service"));
    options.AddPolicy("HumanResourcesMember", policy =>
        policy.RequireClaim("Department", "Human Resources"));
});

В следующем примере используются AuthorizeView компоненты.

Pages/PassCustomerServiceMemberAndHumanResourcesMemberPoliciesWithAuthorizeViews.razor:

@page "/pass-customerservicemember-and-humanresourcesmember-policies-with-authorizeviews"

<h1>Pass 'CustomerServiceMember' and 'HumanResourcesMember' policies with AuthorizeViews</h1>

<AuthorizeView Policy="CustomerServiceMember">
    <Authorized>
        <p>User: @context.User.Identity?.Name</p>
        <AuthorizeView Policy="HumanResourcesMember" Context="innerContext">
            <Authorized>
                <p>
                    You satisfy the 'CustomerServiceMember' and 'HumanResourcesMember' policies.
                </p>
            </Authorized>
            <NotAuthorized>
                <p>
                    You satisfy the 'CustomerServiceMember' policy, but you <b>don't</b> satisfy 
                    the 'HumanResourcesMember' policy.
                </p>
            </NotAuthorized>
        </AuthorizeView>
    </Authorized>
    <NotAuthorized>
        <p>
            You <b>don't</b> satisfy the 'CustomerServiceMember' policy.
        </p>
    </NotAuthorized>
</AuthorizeView>

В следующем примере используются [Authorize] атрибуты.

Pages/PassCustomerServiceMemberAndHumanResourcesMemberPoliciesWithAuthorizeAttributes.razor:

@page "/pass-customerservicemember-and-humanresourcesmember-policies-with-authorize-attributes"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]
@attribute [Authorize(Policy = "HumanResourcesMember")]

<h1>
    Pass 'CustomerServiceMember' and 'HumanResourcesMember' policies with [Authorize] attributes
</h1>

<p>
    You satisfy the 'CustomerServiceMember' and 'HumanResourcesMember' policies.
</p>

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

Чувствительность к регистру утверждения

Значения утверждений сравниваются с помощьюStringComparison.Ordinal. Это означает, что Admin (верхний регистр) и A (нижний регистрadmina) всегда рассматриваются как разные роли, независимо от того, какой обработчик проверки подлинности создал удостоверение.

Сравнение типов утверждений (используется для поиска утверждений ролей по типу утверждения, например http://schemas.microsoft.com/ws/2008/06/identity/claims/role) может быть чувствительным к регистру или нечувствительным, в зависимости от реализации ClaimsIdentity. С Майкрософт.IdentityModel в ASP.NET Core 8.0 или более поздней версии (используется AddJwtBearer, AddOpenIdConnect, AddWsFederation и AddМайкрософтIdentityWebApp/AddМайкрософтIdentityWebApi), CaseSensitiveClaimsIdentity создается во время проверки токена, использующего сопоставление типов утверждений с учетом регистра.

Значение по умолчанию ClaimsIdentity, предоставляемое средой выполнения .NET (используемой в большинстве случаев, включая все потоки на основе cookie) по-прежнему использует сопоставление типов утверждений без учета регистра.

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

Дополнительные ресурсы