ASP.NET Core po stronie serwera i Blazor Web App dodatkowe scenariusze zabezpieczeń
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
W tym artykule wyjaśniono, jak skonfigurować po stronie Blazor serwera dodatkowe scenariusze zabezpieczeń, w tym sposób przekazywania tokenów do Blazor aplikacji.
Uwaga
Przykłady kodu w tym artykule przyjmują typy odwołań dopuszczających wartość null (NRTs) i statyczną analizę stanu null kompilatora platformy .NET, które są obsługiwane w programie ASP.NET Core na platformie .NET 6 lub nowszym. W przypadku określania wartości docelowej ASP.NET Core 5.0 lub starszej usuń oznaczenie typu null (?
) z string?
typów , TodoItem[]?
, WeatherForecast[]?
i IEnumerable<GitHubBranch>?
w przykładach artykułu.
Przekazywanie tokenów do aplikacji po stronie Blazor serwera
Aktualizowanie tej sekcji dla programu Blazor Web Apps oczekuje na aktualizację sekcji dotyczącej przekazywania tokenów w programie Blazor Web Apps (dotnet/AspNetCore.Docs
#31691). Aby uzyskać więcej informacji, zobacz Problem z zapewnianiem tokenu dostępu do klienta HttpClient w trybie interaktywnym serwera (dotnet/aspnetcore
#52390).
W przypadku Blazor Serverprogramu wyświetl wersję 7.0 tego artykułu.
Tokeny dostępne poza Razor składnikami aplikacji po stronie Blazor serwera można przekazać do składników przy użyciu podejścia opisanego w tej sekcji. W przykładzie w tej sekcji skoncentrowano się na przekazywaniu tokenów tokenów tokenów Blazor (XSRF) dostępu, odświeżania i ochrony przed żądaniami (XSRF), ale podejście jest prawidłowe dla innego stanu kontekstu HTTP.
Uwaga
Przekazywanie tokenu XSRF do Razor składników jest przydatne w scenariuszach, w których składniki POST do Identity lub innych punktów końcowych, które wymagają weryfikacji. Jeśli aplikacja wymaga tylko tokenów dostępu i odświeżania, możesz usunąć kod tokenu XSRF z poniższego przykładu.
Uwierzytelnij aplikację tak, jak w przypadku zwykłych Razor stron lub aplikacji MVC. Aprowizuj i zapisz tokeny w uwierzytelnieniu cookie.
W pliku 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);
});
W pliku 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);
});
W pliku 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);
});
Opcjonalnie dodatkowe zakresy są dodawane z elementem options.Scope.Add("{SCOPE}");
, gdzie {SCOPE}
symbol zastępczy jest dodatkowym zakresem do dodania.
Zdefiniuj usługę dostawcy tokenów o określonym zakresie , która może być używana w Blazor aplikacji do rozpoznawania tokenów z iniekcji zależności (DI).
TokenProvider.cs
:
public class TokenProvider
{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
public string? XsrfToken { get; set; }
}
Program
W pliku dodaj usługi dla:
- IHttpClientFactory: używany w
WeatherForecastService
klasie, która uzyskuje dane pogodowe z interfejsu API serwera z tokenem dostępu. TokenProvider
: przechowuje tokeny dostępu i odświeżania.
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();
W Startup.ConfigureServices
pliku Startup.cs
dodaj usługi dla:
- IHttpClientFactory: używany w
WeatherForecastService
klasie, która uzyskuje dane pogodowe z interfejsu API serwera z tokenem dostępu. TokenProvider
: przechowuje tokeny dostępu i odświeżania.
services.AddHttpClient();
services.AddScoped<TokenProvider>();
Zdefiniuj klasę do przekazania początkowego stanu aplikacji przy użyciu tokenów dostępu i odświeżania.
InitialApplicationState.cs
:
public class InitialApplicationState
{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
public string? XsrfToken { get; set; }
}
Pages/_Host.cshtml
W pliku utwórz i wystąpienie InitialApplicationState
i przekaż go jako parametr do aplikacji:
Pages/_Layout.cshtml
W pliku utwórz i wystąpienie InitialApplicationState
i przekaż go jako parametr do aplikacji:
Pages/_Host.cshtml
W pliku utwórz i wystąpienie InitialApplicationState
i przekaż go jako parametr do aplikacji:
@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" ... />
W składniku App
(App.razor
) rozwiąż usługę i zainicjuj ją przy użyciu danych z parametru :
@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();
}
}
Uwaga
Alternatywą do przypisania stanu początkowego do TokenProvider
poprzedniego przykładu jest skopiowanie danych do usługi OnInitializedAsync o określonym zakresie w celu użycia w całej aplikacji.
Dodaj odwołanie do pakietu do aplikacji dla Microsoft.AspNet.WebApi.Client
pakietu NuGet.
Uwaga
Aby uzyskać instrukcje dodawania pakietów do aplikacji .NET, zobacz artykuły w sekcji Instalowanie pakietów i zarządzanie nimi w temacie Przepływ pracy użycia pakietów (dokumentacja programu NuGet). Sprawdź prawidłowe wersje pakietów pod adresem NuGet.org.
W usłudze, która tworzy bezpieczne żądanie interfejsu API, należy wstrzyknąć dostawcę tokenu i pobrać token dla żądania interfejsu 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>();
}
}
W przypadku tokenu XSRF przekazanego do składnika wprowadź TokenProvider
token XSRF i dodaj go do żądania POST. Poniższy przykład dodaje token do punktu końcowego wylogowania POST. W poniższym przykładzie jest to, że punkt końcowy wylogowania (Areas/Identity/Pages/Account/Logout.cshtml
szkielet z aplikacją) nie określa IgnoreAntiforgeryTokenAttribute elementu (@attribute [IgnoreAntiforgeryToken]
), ponieważ wykonuje pewną akcję oprócz normalnej operacji wylogowania, która musi być chroniona. Punkt końcowy wymaga prawidłowego tokenu XSRF, aby pomyślnie przetworzyć żądanie.
W składniku, który przedstawia przycisk Wyloguj się autoryzowanym użytkownikom:
@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>
Ustawianie schematu uwierzytelniania
W przypadku aplikacji, która używa więcej niż jednego oprogramowania pośredniczącego uwierzytelniania i w związku z tym ma więcej niż jeden schemat uwierzytelniania, schemat, który Blazor używa, można jawnie ustawić w konfiguracji punktu końcowego Program
pliku. W poniższym przykładzie ustawiono schemat OpenID Connect (OIDC):
W przypadku aplikacji, która używa więcej niż jednego oprogramowania pośredniczącego uwierzytelniania i w związku z tym ma więcej niż jeden schemat uwierzytelniania, schemat, który Blazor używa, można jawnie ustawić w konfiguracji punktu końcowego Startup.cs
programu . W poniższym przykładzie ustawiono schemat 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
});
W przypadku aplikacji, która używa więcej niż jednego oprogramowania pośredniczącego uwierzytelniania i w związku z tym ma więcej niż jeden schemat uwierzytelniania, schemat, który Blazor używa, można jawnie ustawić w konfiguracji punktu końcowego Startup.Configure
programu . W poniższym przykładzie ustawiono schemat identyfikatora Entra firmy Microsoft:
endpoints.MapBlazorHub().RequireAuthorization(
new AuthorizeAttribute
{
AuthenticationSchemes = AzureADDefaults.AuthenticationScheme
});
Używanie punktów końcowych openID Connect (OIDC) w wersji 2.0
W wersjach ASP.NET Core wcześniejszych niż 5.0 biblioteka uwierzytelniania i Blazor szablony używają punktów końcowych OpenID Connect (OIDC) w wersji 1.0. Aby użyć punktu końcowego w wersji 2.0 z wersjami ASP.NET Core przed wersją 5.0, skonfiguruj OpenIdConnectOptions.Authority opcję w pliku OpenIdConnectOptions:
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme,
options =>
{
options.Authority += "/v2.0";
}
Alternatywnie ustawienie można ustawić w pliku ustawień aplikacji (appsettings.json
):
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
...
}
}
Jeśli tacking na segmencie do urzędu nie jest odpowiedni dla dostawcy OIDC aplikacji, na przykład z dostawcami innych niż ME-ID, ustaw Authority właściwość bezpośrednio. Ustaw właściwość w OpenIdConnectOptions pliku ustawień aplikacji lub w pliku ustawień aplikacji za pomocą Authority klucza.
Zmiany kodu
Lista oświadczeń w zmianach tokenu identyfikatora dla punktów końcowych w wersji 2.0. Dokumentacja firmy Microsoft dotycząca zmian została wycofana, ale wskazówki dotyczące oświadczeń w tokenie identyfikatora są dostępne w dokumentacji oświadczeń tokenu identyfikatora.
Ponieważ zasoby są określone w identyfikatorach URI zakresu dla punktów końcowych w wersji 2.0, usuń OpenIdConnectOptions.Resource ustawienie właściwości w pliku OpenIdConnectOptions:
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => { ... options.Resource = "..."; // REMOVE THIS LINE ... }
Identyfikator URI identyfikatora aplikacji
- W przypadku korzystania z punktów końcowych w wersji 2.0 interfejsy API definiują
App ID URI
element , który ma reprezentować unikatowy identyfikator interfejsu API. - Wszystkie zakresy obejmują identyfikator URI identyfikatora aplikacji jako prefiks, a punkty końcowe w wersji 2.0 emitują tokeny dostępu przy użyciu identyfikatora URI identyfikatora aplikacji jako odbiorców.
- W przypadku korzystania z punktów końcowych w wersji 2.0 identyfikator klienta skonfigurowany w interfejsie API serwera zmienia się z identyfikatora aplikacji interfejsu API (identyfikator klienta) na identyfikator URI identyfikatora aplikacji.
appsettings.json
:
{
"AzureAd": {
...
"ClientId": "https://{TENANT}.onmicrosoft.com/{PROJECT NAME}"
...
}
}
Identyfikator URI identyfikatora aplikacji do użycia można znaleźć w opisie rejestracji aplikacji dostawcy OIDC.
Obsługa obwodu w celu przechwytywania użytkowników dla usług niestandardowych
Użyj elementu , CircuitHandler aby przechwycić użytkownika z obiektu AuthenticationStateProvider i ustawić użytkownika w usłudze. Jeśli chcesz zaktualizować użytkownika, zarejestruj wywołanie zwrotne i AuthenticationStateChanged w kolejce, Task aby uzyskać nowego użytkownika i zaktualizować usługę. W poniższym przykładzie pokazano podejście.
W poniższym przykładzie:
- OnConnectionUpAsync jest wywoływany za każdym razem, gdy obwód ponownie łączy się, ustawiając użytkownika na okres istnienia połączenia. Tylko metoda jest wymagana OnConnectionUpAsync , chyba że zaimplementujesz aktualizacje za pośrednictwem programu obsługi dla zmian uwierzytelniania (
AuthenticationChanged
w poniższym przykładzie). - OnCircuitOpenedAsync Jest wywoływana w celu dołączenia zmienionej procedury obsługi uwierzytelniania,
AuthenticationChanged
, w celu zaktualizowania użytkownika. UpdateAuthentication
Blokcatch
zadania nie podejmuje żadnych działań dotyczących wyjątków, ponieważ nie ma możliwości raportowania wyjątków w tym momencie wykonywania kodu. Jeśli wyjątek jest zgłaszany z zadania, wyjątek jest zgłaszany w innym miejscu w aplikacji.
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;
}
}
W pliku Program
:
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;
...
builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());
W Startup.ConfigureServices
pliku :Startup.cs
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;
...
services.AddScoped<UserService>();
services.TryAddEnumerable(
ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());
Użyj usługi w składniku, aby uzyskać użytkownika:
@inject UserService UserService
<h1>Hello, @(UserService.GetUser().Identity?.Name ?? "world")!</h1>
Aby ustawić użytkownika w oprogramowania pośredniczącego dla mvC, Razor stron i w innych scenariuszach ASP.NET Core, wywołaj SetUser
UserService
w niestandardowym oprogramowania pośredniczącego po uruchomieniu oprogramowania pośredniczącego uwierzytelniania lub ustaw użytkownika z implementacją IClaimsTransformation . Poniższy przykład stosuje podejście oprogramowania pośredniczącego.
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);
}
}
Bezpośrednio przed wywołaniem app.MapRazorComponents<App>()
metody w Program
pliku wywołaj oprogramowanie pośredniczące:
Bezpośrednio przed wywołaniem app.MapBlazorHub()
metody w Program
pliku wywołaj oprogramowanie pośredniczące:
Bezpośrednio przed wywołaniem metody app.MapBlazorHub()
w Startup.Configure
programie Startup.cs
wywołaj oprogramowanie pośredniczące:
app.UseMiddleware<UserServiceMiddleware>();
Dostęp AuthenticationStateProvider
do oprogramowania pośredniczącego żądań wychodzących
Dostęp AuthenticationStateProvider do elementu z DelegatingHandler elementu dla HttpClient utworzonego IHttpClientFactory za pomocą można uzyskać w rozwiązaniu pośredniczącym żądań wychodzących przy użyciu programu obsługi działań obwodu.
Uwaga
Aby uzyskać ogólne wskazówki dotyczące definiowania procedur obsługi delegowania żądań HTTP dla HttpClient wystąpień utworzonych przy użyciu IHttpClientFactory w aplikacjach platformy ASP.NET Core, zobacz następujące sekcje w temacie Make HTTP requests using IHttpClientFactory in ASP.NET Core (Tworzenie żądań HTTP przy użyciu klasy IHttpClientFactory w usłudze ASP.NET Core):
W poniższym przykładzie użyto AuthenticationStateProvider metody dołączania niestandardowego nagłówka nazwy użytkownika dla uwierzytelnionych użytkowników do żądań wychodzących.
Najpierw zaimplementuj klasę CircuitServicesAccessor
w następującej Blazor sekcji artykułu wstrzykiwania zależności (DI):
Uzyskiwanie dostępu do usług po stronie Blazor serwera z innego zakresu di
Użyj elementu , CircuitServicesAccessor
aby uzyskać dostęp do AuthenticationStateProvider elementu w implementacji 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
W pliku zarejestruj AuthenticationStateHandler
program obsługi i dodaj do programu IHttpClientFactory , który tworzy HttpClient wystąpienia:
builder.Services.AddTransient<AuthenticationStateHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<AuthenticationStateHandler>();