Дополнительные сценарии безопасности на стороне сервера ASP.NET Core Blazor
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 8 этой статьи.
В этой статье объясняется, как настроить серверную сторону Blazor для дополнительных сценариев безопасности, включая передачу маркеров Blazor в приложение.
Примечание.
Примеры кода в этой статье используют типы ссылок, допускающие значение NULL (NRTs) и статический анализ состояния .NET компилятора NULL, которые поддерживаются в ASP.NET Core в .NET 6 или более поздней версии. При назначении ASP.NET Core 5.0 или более ранних версий удалите обозначение?
типа NULL () из string?
, TodoItem[]?
WeatherForecast[]?
и IEnumerable<GitHubBranch>?
типы в примерах статьи.
Передача маркеров в серверное Blazor приложение
Обновление этого раздела для Blazor Web Apps ожидает обновления раздела об передаче маркеров в Blazor Web Apps (dotnet/AspNetCore.Docs
#31691). Дополнительные сведения см. в разделе "Проблема предоставления маркера доступа к HttpClient" в интерактивном режиме сервера (dotnet/aspnetcore
#52390).
Для Blazor Serverэтого раздела см. версию 7.0 этой статьи.
Маркеры, доступные за пределами Razor компонентов в серверном Blazor приложении, можно передать компонентам с помощью подхода, описанного в этом разделе. В примере этого раздела основное внимание уделяется передаче маркеров Blazor маркеров маркера XSRF, обновлению и защите от запросов (XSRF), но этот подход действителен для другого состояния контекста HTTP.
Примечание.
Передача маркера Razor XSRF компонентам полезна в сценариях, когда компоненты POST Identity или другие конечные точки, требующие проверки. Если приложению требуются только маркеры доступа и обновления, можно удалить код маркера XSRF из следующего примера.
Проверка подлинности приложения, как и в обычном Razor приложении 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). Проверьте правильность версий пакета на сайте 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;
var request = new HttpRequestMessage(HttpMethod.Get,
"https://localhost:5003/WeatherForecast");
request.Headers.Add("Authorization", $"Bearer {token}");
var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ??
Array.Empty<WeatherForecast>();
}
}
Для маркера XSRF, переданного компоненту, вставляется TokenProvider
и добавляется маркер XSRF в запрос POST. В следующем примере маркер добавляется в конечную точку выхода POST. Сценарий следующего примера заключается в том, что конечная точка выхода (с шаблонами в приложение) не указывает IgnoreAntiforgeryTokenAttribute (Areas/Identity/Pages/Account/Logout.cshtml
@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 5.0 в библиотеке проверки подлинности и шаблонах Blazor используются конечные точки OpenID Connect (OIDC) версии 1.0. Чтобы использовать конечную точку версии 2.0 с версиями ASP.NET Core до 5.0, настройте параметр 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. Документация Майкрософт по изменениям была прекращена, но рекомендации по утверждениям в маркере идентификатора доступны в справочнике по утверждениям маркера идентификатора.
Поскольку ресурсы указываются в 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. - Универсальный код ресурса (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
чтобы обновить пользователя. - Блок
catch
UpdateAuthentication
задачи не выполняет никаких действий по исключениям, так как на этом этапе выполнения кода невозможно сообщить об исключениях. Если исключение создается из задачи, исключение сообщается в другом месте приложения.
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
пользовательское ПО промежуточного слоя после запуска ПО промежуточного слоя проверки подлинности или задайте пользователю реализацию IClaimsTransformation . В следующем примере используется подход по промежуточного слоя.
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
к по промежуточному по промежуточному слоям исходящего запроса
С AuthenticationStateProvider помощью обработчика действий канала можно получить доступ к созданному с IHttpClientFactory помощью по промежуточного DelegatingHandler HttpClient слоя исходящего запроса.
Примечание.
Общие рекомендации по определению делегирования обработчиков http-запросов HttpClient экземплярами, созданными в IHttpClientFactory приложениях ASP.NET Core, см. в следующих разделах : "Создание HTTP-запросов с помощью IHttpClientFactory" в ASP.NET Core:
В следующем примере используется AuthenticationStateProvider подключение пользовательского заголовка имени пользователя для прошедших проверку подлинности пользователей к исходящим запросам.
Сначала реализуйте CircuitServicesAccessor
класс в следующем разделе статьи Blazor внедрения зависимостей (DI):
Доступ к службам на стороне Blazor сервера из другой области DI
CircuitServicesAccessor
Используйте для доступа к AuthenticationStateProvider реализацииDelegatingHandler.
AuthenticationStateHandler.cs
:
public class AuthenticationStateHandler(
CircuitServicesAccessor circuitServicesAccessor)
: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var authStateProvider = circuitServicesAccessor.Services
.GetRequiredService<AuthenticationStateProvider>();
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>();
ASP.NET Core