Поделиться через


ASP.NET Core на стороне сервера и Blazor Web App дополнительные сценарии безопасности

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см. версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см. версию .NET 9 этой статьи.

Внимание

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

В текущем выпуске см. версию .NET 9 этой статьи.

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

Примечание.

Примеры кода в этой статье используют типы ссылок, допускающие значение NULL (NRTs) и статический анализ состояния .NET компилятора NULL, которые поддерживаются в ASP.NET Core в .NET 6 или более поздней версии. При ориентировании на .NET 5 или более ранние версии, удалите обозначение типа NULL (?) из типов string?, TodoItem[]?, WeatherForecast[]? и IEnumerable<GitHubBranch>? в примерах статьи.

Передать токены в серверное Blazor приложение

Этот раздел относится к Blazor Web Apps. Для Blazor Serverэтого раздела см. версию .NET 7 этой статьи.

Если вы просто хотите использовать маркеры доступа для вызова веб-API из Blazor Web Appименованного HTTP-клиента, см. раздел «Использование обработчика маркеров для вызовов веб-API», в котором объясняется, как использовать DelegatingHandler реализацию для прикрепления маркера доступа пользователя к исходящим запросам. В этом разделе приведено следующее руководство для разработчиков, которым требуются маркеры доступа, маркеры обновления и другие свойства проверки подлинности на стороне сервера для других целей.

Чтобы сохранить маркеры и другие свойства проверки подлинности для использования на стороне сервера, Blazor Web Appрекомендуется использовать IHttpContextAccessor/HttpContext (IHttpContextAccessor, HttpContext). Чтение маркеров из HttpContext, включая использование в качестве каскадного параметра, с использованием IHttpContextAccessor поддерживается для получения маркеров для использования в интерактивном рендеринге на сервере, если маркеры получены во время статической серверной отрисовки (static SSR) или заранее. Однако токены не обновляются, если пользователь проходит проверку подлинности после установки канала, так как токен фиксируется в начале подключения. Кроме того, использование AsyncLocal<T>IHttpContextAccessor означает, что вы должны быть осторожны, чтобы не потерять контекст выполнения перед чтением HttpContext. Дополнительные сведения см. в разделе IHttpContextAccessor/HttpContext в приложениях ASP.NET CoreBlazor.

В классе обслуживания получите доступ к элементам пространства имен Microsoft.AspNetCore.Authentication, чтобы отобразить метод GetTokenAsync на HttpContext. Альтернативный подход, который закомментирован в следующем примере, заключается в вызове AuthenticateAsync на HttpContext. Для возвращенного AuthenticateResult.Properties вызовите GetTokenValue.

using Microsoft.AspNetCore.Authentication;

public class AuthenticationProcessor(IHttpContextAccessor httpContextAccessor)
{
    public async Task<string?> GetAccessToken()
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw new Exception("HttpContext not available");
        }

        // Approach 1: Call 'GetTokenAsync'
        var accessToken = await httpContextAccessor.HttpContext
            .GetTokenAsync("access_token");

        // Approach 2: Authenticate the user and call 'GetTokenValue'
        /*
        var authResult = await httpContextAccessor.HttpContext.AuthenticateAsync();
        var accessToken = authResult?.Properties?.GetTokenValue("access_token");
        */

        return accessToken;
    }
}

Служба зарегистрирована в файле проекта Program сервера:

builder.Services.AddScoped<AuthenticationProcessor>();

AuthenticationProcessor можно внедрить в серверные службы, например, в DelegatingHandler для предварительно настроенного HttpClient. Следующий пример предназначен только для демонстрационных целей или в случае необходимости выполнять специальную обработку в AuthenticationProcessor службе, так как вы можете просто внедрить IHttpContextAccessor и получить маркер непосредственно для вызова внешних веб-API (дополнительные сведения об использовании IHttpContextAccessor непосредственно для вызова веб-API см. в разделе "Использование обработчика маркеров для вызовов веб-API ").

using System.Net.Http.Headers;

public class TokenHandler(AuthenticationProcessor authProcessor) : 
    DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var accessToken = authProcessor.GetAccessToken();

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);

        return await base.SendAsync(request, cancellationToken);
    }
}

Обработчик токенов регистрируется и выступает в качестве делегированного обработчика для именованного HTTP-клиента в файле Program.

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("ExternalApi",
      client => client.BaseAddress = new Uri(builder.Configuration["ExternalApiUri"] ?? 
          throw new Exception("Missing base address!")))
      .AddHttpMessageHandler<TokenHandler>();

Осторожность

Убедитесь, что маркеры никогда не передаются и не обрабатываются клиентом (.Client проектом), например, в компоненте, который принимает интерактивный метод отрисовки и обрабатывается клиентом или службой на стороне клиента. Всегда вызывайте клиентом сервер (проект), чтобы обрабатывать запросы с маркерами. Маркеры и другие данные проверки подлинности никогда не должны покидать сервер.

Для интерактивных компонентов аутентификации, см. раздел ASP.NET Core проверка подлинности и авторизацияBlazor, демонстрирующий, как оставить токены доступа и другие свойства аутентификации на сервере. Кроме того, рассмотрите возможность внедрения шаблона Backend-for-Frontend (BFF), который принимает аналогичную структуру вызовов и описывается в статьях: Blazor Web App для поставщиков OIDC и "Защита ASP.NET Core с помощью Microsoft Entra ID" для Microsoft Blazor Web App Web с помощью Entra.

Использование обработчика маркеров для вызовов веб-API

Следующий подход направлен на присоединение токена доступа пользователя к исходящим запросам, в частности для вызова внешних приложений web API. Этот подход показан для Blazor Web App внедрения глобальной интерактивной отрисовки сервера, но тот же общий подход применяется к Blazor Web App, которые используют глобальный режим интерактивной автоматической отрисовки. Важно помнить, что доступ к HttpContext с помощью IHttpContextAccessor осуществляется только на сервере.

Чтобы ознакомиться с демонстрацией рекомендаций в этом разделе, см. примеры приложений (BlazorWebAppOidc и BlazorWebAppOidcServer) для .NET 8 или более поздних версий в Blazor репозитории примеров GitHub. В примерах используется глобальный интерактивный режим визуализации и аутентификации OIDC с помощью Microsoft Entra без использования пакетов, специфичных для Entra. В примерах показано, как передать маркер доступа JWT для вызова безопасного веб-API.

Платформа удостоверений Майкрософт с веб-пакетами Microsoft Identity для Microsoft Entra ID предоставляет API для вызова веб-API из Blazor Web App с автоматическим управлением токенами и их обновлением. Дополнительные сведения см. в статье "Защита ASP.NET Core Blazor Web App с Microsoft Entra ID и примерах приложений BlazorWebAppEntraBlazorWebAppEntraBff (.NET 9 или более поздние версии) в Blazor репозитории образцов GitHub.

Подкласс DelegatingHandler для присоединения маркера доступа пользователя к исходящим запросам. Обработчик маркеров выполняется только на сервере, поэтому использование HttpContext безопасно.

TokenHandler.cs:

using System.Net.Http.Headers;
using Microsoft.AspNetCore.Authentication;

public class TokenHandler(IHttpContextAccessor httpContextAccessor) : 
    DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw new Exception("HttpContext not available");
        }

        var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");

        if (accessToken is null)
        {
            throw new Exception("No access token");
        }

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);

        return await base.SendAsync(request, cancellationToken);
    }
}

Примечание.

Инструкции по получению доступа к AuthenticationStateProvider из DelegatingHandler см. в разделе «Доступ к AuthenticationStateProvider в промежуточном ПО исходящих запросов».

В файле проекта Program обработчик маркеров (TokenHandler) регистрируется в качестве службы с областью действия и указывается в качестве обработчика сообщений http-клиента с AddHttpMessageHandlerименем.

В следующем примере плейсхолдер {HTTP CLIENT NAME} — это имя HttpClient, а плейсхолдер {BASE ADDRESS} — базовый URI веб-API. Дополнительные сведения см. в разделе AddHttpContextAccessorBlazor.

В Program.cs:

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("{HTTP CLIENT NAME}",
      client => client.BaseAddress = new Uri("{BASE ADDRESS}"))
      .AddHttpMessageHandler<TokenHandler>();

Пример:

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("ExternalApi",
      client => client.BaseAddress = new Uri("https://localhost:7277"))
      .AddHttpMessageHandler<TokenHandler>();

Вы можете указать базовый адрес клиента HTTP из конфигурации с помощью builder.Configuration["{CONFIGURATION KEY}"], где {CONFIGURATION KEY} — это ключ конфигурации.

new Uri(builder.Configuration["ExternalApiUri"] ?? throw new IOException("No URI!"))

В appsettings.json укажите ExternalApiUri. В следующем примере значение присваивается адресу localhost для внешнего веб-API как https://localhost:7277:

"ExternalApiUri": "https://localhost:7277"

На этом этапе HttpClient, созданный компонентом, может выполнять безопасные запросы веб-API. В следующем примере {REQUEST URI} является относительным URI запроса, а заполнитель {HTTP CLIENT NAME} — это имя HttpClient:

using var request = new HttpRequestMessage(HttpMethod.Get, "{REQUEST URI}");
var client = ClientFactory.CreateClient("{HTTP CLIENT NAME}");
using var response = await client.SendAsync(request);

Пример:

using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast");
var client = ClientFactory.CreateClient("ExternalApi");
using var response = await client.SendAsync(request);

Планируются дополнительные функции для Blazor, которые отслеживаются Access AuthenticationStateProvider в промежуточном слое обработки исходящих запросов (dotnet/aspnetcore #52379). Проблема с предоставлением токена доступа HttpClient в интерактивном серверном режиме (dotnet/aspnetcore #52390) — это закрытая проблема, содержащая полезные обсуждения и потенциальные стратегии обходного решения для сложных случаев использования.

Маркеры, доступные за пределами Razor компонентов в серверном Blazor приложении, можно передать компонентам, используя подход, описанный в этом разделе. В примере этого раздела основное внимание уделяется передаче токенов доступа, обновления и токена защиты от поддельных запросов (XSRF) в приложение, но этот подход также действителен для других состояний контекста HTTP.

Примечание.

Передача маркера XSRF компонентам Razor полезна в сценариях, когда компоненты отправляют POST-запросы на Identity или на другие конечные точки, которые требуют проверки. Если приложению требуются только маркеры доступа и обновления, можно удалить код маркера XSRF из следующего примера.

Проверьте подлинность приложения, как делаете это в обычном приложении Pages или MVC. Предоставьте и сохраните маркеры для проверки подлинности cookie.

В файле Program:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

В Startup.cs:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

В Startup.cs:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

При необходимости можно добавить дополнительные области с помощью options.Scope.Add("{SCOPE}");, где {SCOPE} обозначает дополнительную область для добавления.

Определите службу поставщика маркеров, имеющую область действия, которую можно использовать в Blazor приложении для получения маркеров из внедрения зависимостей (DI).

TokenProvider.cs:

public class TokenProvider
{
    public string? AccessToken { get; set; }
    public string? RefreshToken { get; set; }
    public string? XsrfToken { get; set; }
}

В файле Program добавьте службы для:

  • IHttpClientFactory: используется в WeatherForecastService классе, который получает данные о погоде из API сервера с токеном доступа.
  • TokenProvider: содержит токены доступа и обновления.
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();

В Startup.ConfigureServices из Startup.cs добавьте услуги для:

  • IHttpClientFactory: используется в WeatherForecastService классе, который получает данные о погоде из API сервера с токеном доступа.
  • TokenProvider: содержит токены доступа и обновления.
services.AddHttpClient();
services.AddScoped<TokenProvider>();

Определите класс для передачи исходного состояния приложения с маркерами доступа и обновления.

InitialApplicationState.cs:

public class InitialApplicationState
{
    public string? AccessToken { get; set; }
    public string? RefreshToken { get; set; }
    public string? XsrfToken { get; set; }
}

В файле Pages/_Host.cshtml создайте экземпляр InitialApplicationState и передайте его в качестве параметра в приложение:

В файле Pages/_Layout.cshtml создайте экземпляр InitialApplicationState и передайте его в качестве параметра в приложение:

В файле Pages/_Host.cshtml создайте экземпляр InitialApplicationState и передайте его в качестве параметра в приложение:

@using Microsoft.AspNetCore.Authentication
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf

...

@{
    var tokens = new InitialApplicationState
    {
        AccessToken = await HttpContext.GetTokenAsync("access_token"),
        RefreshToken = await HttpContext.GetTokenAsync("refresh_token"),
        XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken
    };
}

<component ... param-InitialState="tokens" ... />

В компоненте App (App.razor) разрешите службу и инициализируйте ее с помощью данных из параметра:

@inject TokenProvider TokenProvider

...

@code {
    [Parameter]
    public InitialApplicationState? InitialState { get; set; }

    protected override Task OnInitializedAsync()
    {
        TokenProvider.AccessToken = InitialState?.AccessToken;
        TokenProvider.RefreshToken = InitialState?.RefreshToken;
        TokenProvider.XsrfToken = InitialState?.XsrfToken;

        return base.OnInitializedAsync();
    }
}

Примечание.

Альтернативой назначению начального состояния в TokenProvider в приведенном выше примере является копирование данных в ограниченную областью действия службу для использования во всем приложении OnInitializedAsync.

Добавьте в приложение ссылку на пакет NuGet Microsoft.AspNet.WebApi.Client.

Примечание.

Рекомендации по добавлению пакетов в приложения .NET см. в статьях в разделе "Установка пакетов и управление пакетами" в рабочем процессе потребления пакетов (документация NuGet). Проверьте правильные версии пакетов в NuGet.org.

В службе, выполняющей безопасный API-запрос, используйте поставщика токенов и извлеките токен для выполнения API-запроса.

WeatherForecastService.cs:

using System;
using System.Net.Http;
using System.Threading.Tasks;

public class WeatherForecastService
{
    private readonly HttpClient http;
    private readonly TokenProvider tokenProvider;

    public WeatherForecastService(IHttpClientFactory clientFactory, 
        TokenProvider tokenProvider)
    {
        http = clientFactory.CreateClient();
        this.tokenProvider = tokenProvider;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        var token = tokenProvider.AccessToken;
        using var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://localhost:5003/WeatherForecast");
        request.Headers.Add("Authorization", $"Bearer {token}");
        using var response = await http.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ?? 
            Array.Empty<WeatherForecast>();
    }
}

Для маркера XSRF, переданного компоненту, вставляется TokenProvider и добавляется маркер XSRF в запрос POST. В следующем примере токен добавляется в конечную точку для выхода POST. В следующем примере конечная точка выхода (Areas/Identity/Pages/Account/Logout.cshtml, встроена в приложение как шаблон) не указывает IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]), так как она выполняет дополнительные действия помимо стандартного выхода, чтобы обеспечить их безопасность. Для успешной обработки запроса конечная точка требует допустимого маркера XSRF.

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

@inject TokenProvider TokenProvider

...

<AuthorizeView>
    <Authorized>
        <form action="/Identity/Account/Logout?returnUrl=%2F" method="post">
            <button class="nav-link btn btn-link" type="submit">Logout</button>
            <input name="__RequestVerificationToken" type="hidden" 
                value="@TokenProvider.XsrfToken">
        </form>
    </Authorized>
    <NotAuthorized>
        ...
    </NotAuthorized>
</AuthorizeView>

Настройка схемы проверки подлинности

Для приложения, использующего несколько промежуточных слоев проверки подлинности и, следовательно, имеющего несколько схем проверки подлинности, схема, которую использует Blazor, может быть явно задана в конфигурации конечной точки в файле Program. В следующем примере задается схема OpenID Connect (OIDC):

Для приложения, использующего более одного ПО промежуточного слоя для проверки подлинности и, таким образом, имеющего несколько схем проверки подлинности, схему, которую использует Blazor, можно явно задать в конфигурации конечной точки Startup.cs. В следующем примере задается схема OpenID Connect (OIDC):

using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapRazorComponents<App>().RequireAuthorization(
    new AuthorizeAttribute
    {
        AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
    })
    .AddInteractiveServerRenderMode();
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapBlazorHub().RequireAuthorization(
    new AuthorizeAttribute 
    {
        AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
    });

Для приложения, использующего более одного ПО промежуточного слоя для проверки подлинности и, таким образом, имеющего несколько схем проверки подлинности, схему, которую использует Blazor, можно явно задать в конфигурации конечной точки Startup.Configure. В следующем примере задается схема идентификатора Microsoft Entra ID:

endpoints.MapBlazorHub().RequireAuthorization(
    new AuthorizeAttribute 
    {
        AuthenticationSchemes = AzureADDefaults.AuthenticationScheme
    });

Использование конечных точек OpenID Connect (OIDC) версии 2.0

В версиях ASP.NET Core до .NET 5 библиотека проверки подлинности и Blazor шаблоны используют конечные точки OpenID Connect версии 1.0. Чтобы использовать конечную точку версии 2.0 с версиями ASP.NET Core до .NET 5, настройте параметр OpenIdConnectOptions.Authority в параметре OpenIdConnectOptions.

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    }

Кроме того, параметр можно задать в файле параметров приложения (appsettings.json):

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

Если добавление сегмента к адресу недопустимо для поставщика OIDC приложения, например, если используется провайдер, не относящийся к ME-ID, задайте свойство напрямую Authority. Задайте свойство либо в OpenIdConnectOptions, либо в файле параметров приложения с помощью ключа Authority.

Изменения в коде

  • Список утверждений в токене идентификатора отличается для конечных точек версии 2.0. Документация Microsoft по изменениям устарела, но рекомендации по утверждениям в токене идентификатора доступны в справочнике по утверждениям токена идентификатора.

  • Поскольку ресурсы указываются в рамках URI для конечных точек версии 2.0, удалите настройку свойства OpenIdConnectOptions.Resource в OpenIdConnectOptions.

    services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => 
        {
            ...
            options.Resource = "...";    // REMOVE THIS LINE
            ...
        }
    

URI идентификатора приложения

  • При использовании конечных точек версии 2.0 в интерфейсах API определяется код App ID URI, который должен представлять уникальный идентификатор интерфейса API.
  • Все области (scopes) содержат URI идентификатора приложения в качестве префикса, а конечные точки версии 2.0 создают маркеры доступа с URI идентификатора приложения в качестве целевой аудитории.
  • При использовании конечных точек версии 2.0 идентификатор клиента, настроенный в API сервера, изменяется с идентификатора приложения API (идентификатора клиента) на код URI идентификатора приложения.

appsettings.json:

{
  "AzureAd": {
    ...
    "ClientId": "https://{TENANT}.onmicrosoft.com/{PROJECT NAME}"
    ...
  }
}

URI идентификатора приложения, который нужно использовать, можно найти в описании регистрации приложения провайдера OIDC.

Обработчик цепей для перехвата пользователей для пользовательских служб

Используйте CircuitHandler, чтобы захватить пользователя из AuthenticationStateProvider и установить пользователя в службе. Если вы хотите обновить пользователя, зарегистрируйте обратный вызов на AuthenticationStateChanged и поставьте Task в очередь, чтобы получить нового пользователя и обновить сервис. Такой подход демонстрируется в приведенном ниже примере.

В следующем примере :

  • OnConnectionUpAsync вызывается каждый раз, когда канал повторно подключается, задав пользователю время существования подключения. OnConnectionUpAsync Этот метод требуется только в том случае, если вы не реализуете обновления с помощью обработчика изменений проверки подлинности (AuthenticationChangedв следующем примере).
  • OnCircuitOpenedAsync вызывается для подключения обработчика изменения проверки подлинности AuthenticationChanged, чтобы обновить пользователя.
  • Блок catchUpdateAuthentication задачи не выполняет никаких действий по исключениям, так как на этом этапе выполнения кода невозможно сообщить об исключениях. Если из задачи выбрасывается исключение, оно фиксируется в другом месте приложения.

UserService.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService
{
    private ClaimsPrincipal currentUser = new(new ClaimsIdentity());

    public ClaimsPrincipal GetUser() => currentUser;

    internal void SetUser(ClaimsPrincipal user)
    {
        if (currentUser != user)
        {
            currentUser = user;
        }
    }
}

internal sealed class UserCircuitHandler(
        AuthenticationStateProvider authenticationStateProvider,
        UserService userService) 
        : CircuitHandler, IDisposable
{
    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        authenticationStateProvider.AuthenticationStateChanged += 
            AuthenticationChanged;

        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    private void AuthenticationChanged(Task<AuthenticationState> task)
    {
        _ = UpdateAuthentication(task);

        async Task UpdateAuthentication(Task<AuthenticationState> task)
        {
            try
            {
                var state = await task;
                userService.SetUser(state.User);
            }
            catch
            {
            }
        }
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        var state = await authenticationStateProvider.GetAuthenticationStateAsync();
        userService.SetUser(state.User);
    }

    public void Dispose()
    {
        authenticationStateProvider.AuthenticationStateChanged -= 
            AuthenticationChanged;
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public ClaimsPrincipal GetUser()
    {
        return currentUser;
    }

    internal void SetUser(ClaimsPrincipal user)
    {
        if (currentUser != user)
        {
            currentUser = user;
        }
    }
}

internal sealed class UserCircuitHandler : CircuitHandler, IDisposable
{
    private readonly AuthenticationStateProvider authenticationStateProvider;
    private readonly UserService userService;

    public UserCircuitHandler(
        AuthenticationStateProvider authenticationStateProvider,
        UserService userService)
    {
        this.authenticationStateProvider = authenticationStateProvider;
        this.userService = userService;
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        authenticationStateProvider.AuthenticationStateChanged += 
            AuthenticationChanged;

        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    private void AuthenticationChanged(Task<AuthenticationState> task)
    {
        _ = UpdateAuthentication(task);

        async Task UpdateAuthentication(Task<AuthenticationState> task)
        {
            try
            {
                var state = await task;
                userService.SetUser(state.User);
            }
            catch
            {
            }
        }
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        var state = await authenticationStateProvider.GetAuthenticationStateAsync();
        userService.SetUser(state.User);
    }

    public void Dispose()
    {
        authenticationStateProvider.AuthenticationStateChanged -= 
            AuthenticationChanged;
    }
}

В файле Program:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

В Startup.ConfigureServices из Startup.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

services.AddScoped<UserService>();
services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

Используйте службу в компоненте, чтобы получить пользователя:

@inject UserService UserService

<h1>Hello, @(UserService.GetUser().Identity?.Name ?? "world")!</h1>

Чтобы настроить пользователя в промежуточном слое для MVC, Razor Pages и в других сценариях ASP.NET Core, вызовите SetUser в пользовательском промежуточном слое после запуска промежуточного слоя проверки подлинности или установите пользователя с помощью реализации UserService. В следующем примере используется подход промежуточного программного обеспечения.

UserServiceMiddleware.cs:

public class UserServiceMiddleware
{
    private readonly RequestDelegate next;

    public UserServiceMiddleware(RequestDelegate next)
    {
        this.next = next ?? throw new ArgumentNullException(nameof(next));
    }

    public async Task InvokeAsync(HttpContext context, UserService service)
    {
        service.SetUser(context.User);
        await next(context);
    }
}

Непосредственно перед вызовом app.MapRazorComponents<App>() в файле Program вызовите посредническое ПО:

Непосредственно перед вызовом app.MapBlazorHub() в файле Program вызовите посредническое ПО:

Непосредственно перед вызовом app.MapBlazorHub() в Startup.Configure, вызовите промежуточное ПО Startup.cs.

app.UseMiddleware<UserServiceMiddleware>();

Доступ AuthenticationStateProvider в посреднике исходящих запросов

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

Примечание.

Общие рекомендации по определению делегирования обработчиков HTTP-запросов для экземпляров HttpClient, создаваемых с использованием IHttpClientFactory в приложениях ASP.NET Core, см. в следующих разделах документа Создание HTTP-запросов с помощью IHttpClientFactory в ASP.NET Core:

В следующем примере используется AuthenticationStateProvider для добавления заголовка имени пользователя к исходящим запросам для пользователей, прошедших проверку подлинности.

Сначала внедрите CircuitServicesAccessor класс в следующем разделе статьи о Blazor внедрении зависимостей (DI):

Доступ к службам на стороне Blazor сервера из другой области DI

Используйте CircuitServicesAccessor для доступа к AuthenticationStateProvider в реализации DelegatingHandler.

AuthenticationStateHandler.cs:

using Microsoft.AspNetCore.Components.Authorization;

public class AuthenticationStateHandler(
    CircuitServicesAccessor circuitServicesAccessor) 
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authStateProvider = circuitServicesAccessor.Services?
            .GetRequiredService<AuthenticationStateProvider>();

        if (authStateProvider is null)
        {
            throw new Exception("AuthenticationStateProvider not available");
        }

        var authState = await authStateProvider.GetAuthenticationStateAsync();

        var user = authState?.User;

        if (user?.Identity is not null && user.Identity.IsAuthenticated)
        {
            request.Headers.Add("X-USER-IDENTITY-NAME", user.Identity.Name);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

В файле Program зарегистрируйте AuthenticationStateHandler и добавьте обработчик в IHttpClientFactory, который создает экземпляры HttpClient.

builder.Services.AddTransient<AuthenticationStateHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
    .AddHttpMessageHandler<AuthenticationStateHandler>();