Scenari di sicurezza aggiuntivi sul lato server ASP.NET Core Blazor
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Questo articolo illustra come configurare il lato Blazor server per scenari di sicurezza aggiuntivi, tra cui come passare token a un'app Blazor .
Nota
Gli esempi di codice in questo articolo adottano tipi di riferimento nullable (NRT) e l'analisi statica dello stato null del compilatore .NET, supportati in ASP.NET Core in .NET 6 o versione successiva. Quando la destinazione è ASP.NET Core 5.0 o versioni precedenti, rimuovere la designazione di tipo Null (?
) dai string?
tipi , WeatherForecast[]?
TodoItem[]?
, e IEnumerable<GitHubBranch>?
negli esempi dell'articolo.
Passare token a un'app lato Blazor server
L'aggiornamento di questa sezione per Blazor Web Apps è in sospeso nella sezione Update per il passaggio di token in Blazor Web Apps (dotnet/AspNetCore.Docs
#31691). Per altre informazioni, vedere Problema di fornitura di token di accesso a HttpClient in modalità Interactive Server (dotnet/aspnetcore
#52390).
Per Blazor Server, visualizzare la versione 7.0 di questo articolo.
I token disponibili all'esterno dei Razor componenti in un'app lato Blazor server possono essere passati ai componenti con l'approccio descritto in questa sezione. L'esempio di questa sezione è incentrato sul passaggio di token token di accesso, aggiornamento e anti-richiesta (XSRF) all'appBlazor, ma l'approccio è valido per altri stati del contesto HTTP.
Nota
Il passaggio del token XSRF ai Razor componenti è utile negli scenari in cui i componenti POST a Identity o altri endpoint che richiedono la convalida. Se l'app richiede solo token di accesso e aggiornamento, è possibile rimuovere il codice del token XSRF dall'esempio seguente.
Autenticare l'app come si farebbe con una normale Razor app Pages o MVC. Effettuare il provisioning e salvare i token nell'autenticazione cookie.
Nel file 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);
});
In 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);
});
In 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);
});
Facoltativamente, vengono aggiunti altri ambiti con options.Scope.Add("{SCOPE}");
, dove il {SCOPE}
segnaposto è l'ambito aggiuntivo da aggiungere.
Definire un servizio provider di token con ambito che può essere usato all'interno dell'app per risolvere i token dall'inserimento delle dipendenze.Define a scoped token provider service that can be used within the Blazor app to resolve the tokens from dependency injection (DI).
TokenProvider.cs
:
public class TokenProvider
{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
public string? XsrfToken { get; set; }
}
Program
Nel file aggiungere servizi per:
- IHttpClientFactory: usato in una
WeatherForecastService
classe che ottiene i dati meteo da un'API server con un token di accesso. TokenProvider
: contiene i token di accesso e di aggiornamento.
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();
In Startup.ConfigureServices
aggiungere Startup.cs
servizi per:
- IHttpClientFactory: usato in una
WeatherForecastService
classe che ottiene i dati meteo da un'API server con un token di accesso. TokenProvider
: contiene i token di accesso e di aggiornamento.
services.AddHttpClient();
services.AddScoped<TokenProvider>();
Definire una classe per passare lo stato iniziale dell'app con i token di accesso e di aggiornamento.
InitialApplicationState.cs
:
public class InitialApplicationState
{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
public string? XsrfToken { get; set; }
}
Pages/_Host.cshtml
Nel file creare e istanza di InitialApplicationState
e passarlo come parametro all'app:
Pages/_Layout.cshtml
Nel file creare e istanza di InitialApplicationState
e passarlo come parametro all'app:
Pages/_Host.cshtml
Nel file creare e istanza di InitialApplicationState
e passarlo come parametro all'app:
@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
Nel componente (App.razor
) risolvere il servizio e inizializzarlo con i dati del parametro :
@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();
}
}
Nota
Un'alternativa all'assegnazione dello stato iniziale a TokenProvider
nell'esempio precedente consiste nel copiare i dati in un servizio con ambito all'interno di per l'uso OnInitializedAsync nell'app.
Aggiungere un riferimento al pacchetto all'app per il Microsoft.AspNet.WebApi.Client
pacchetto NuGet.
Nota
Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.
Nel servizio che effettua una richiesta API sicura inserire il provider di token e recuperare il token per la richiesta 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>();
}
}
Per un token XSRF passato a un componente, inserire TokenProvider
e aggiungere il token XSRF alla richiesta POST. L'esempio seguente aggiunge il token a un endpoint di disconnessione POST. Lo scenario per l'esempio seguente è che l'endpoint di disconnessione (Areas/Identity/Pages/Account/Logout.cshtml
, sottoposto a scaffolding nell'app) non specifica (IgnoreAntiforgeryTokenAttribute@attribute [IgnoreAntiforgeryToken]
) perché esegue un'azione oltre a una normale operazione di disconnessione che deve essere protetta. L'endpoint richiede un token XSRF valido per elaborare correttamente la richiesta.
In un componente che presenta un pulsante disconnessione per gli utenti autorizzati:
@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>
Impostare lo schema di autenticazione
Per un'app che usa più middleware di autenticazione e pertanto ha più schemi di autenticazione, lo schema usato Blazor può essere impostato in modo esplicito nella configurazione dell'endpoint del Program
file. L'esempio seguente imposta lo schema OpenID Connect (OIDC):
Per un'app che usa più di un middleware di autenticazione e pertanto ha più schemi di autenticazione, lo schema che Blazor usa può essere impostato in modo esplicito nella configurazione dell'endpoint di Startup.cs
. L'esempio seguente imposta lo schema 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
});
Per un'app che usa più di un middleware di autenticazione e pertanto ha più schemi di autenticazione, lo schema che Blazor usa può essere impostato in modo esplicito nella configurazione dell'endpoint di Startup.Configure
. L'esempio seguente imposta lo schema MICROSOFT Entra ID:
endpoints.MapBlazorHub().RequireAuthorization(
new AuthorizeAttribute
{
AuthenticationSchemes = AzureADDefaults.AuthenticationScheme
});
Usare endpoint OIDC (OpenID Connect) v2.0
Nelle versioni di ASP.NET Core precedenti alla 5.0, la libreria di autenticazione e Blazor i modelli usano endpoint OpenID Connect (OIDC) v1.0. Per usare un endpoint v2.0 con versioni di ASP.NET Core precedenti alla 5.0, configurare l'opzione OpenIdConnectOptions.Authority in OpenIdConnectOptions:
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme,
options =>
{
options.Authority += "/v2.0";
}
In alternativa, l'impostazione può essere eseguita nel file delle impostazioni dell'app (appsettings.json
):
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
...
}
}
Se la descrizione di un segmento per l'autorità non è appropriata per il provider OIDC dell'app, ad esempio con provider non ME-ID, impostare direttamente la Authority proprietà. Impostare la proprietà in OpenIdConnectOptions o nel file di impostazioni dell'app con la Authority chiave .
Modifiche al codice
L'elenco delle attestazioni nel token ID cambia per gli endpoint v2.0. La documentazione Microsoft sulle modifiche è stata ritirata, ma le indicazioni sulle attestazioni in un token ID sono disponibili nel riferimento alle attestazioni del token ID.
Poiché le risorse sono specificate negli URI di ambito per gli endpoint v2.0, rimuovere l'impostazione della OpenIdConnectOptions.Resource proprietà in OpenIdConnectOptions:
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => { ... options.Resource = "..."; // REMOVE THIS LINE ... }
URI ID app
- Quando si usano endpoint v2.0, le API definiscono un
App ID URI
oggetto , destinato a rappresentare un identificatore univoco per l'API. - Tutti gli ambiti includono l'URI ID app come prefisso e gli endpoint v2.0 generano token di accesso con l'URI ID app come gruppo di destinatari.
- Quando si usano gli endpoint V2.0, l'ID client configurato nell'API server passa dall'ID applicazione API (ID client) all'URI ID app.
appsettings.json
:
{
"AzureAd": {
...
"ClientId": "https://{TENANT}.onmicrosoft.com/{PROJECT NAME}"
...
}
}
È possibile trovare l'URI ID app da usare nella descrizione della registrazione dell'app del provider OIDC.
Gestore del circuito per acquisire gli utenti per i servizi personalizzati
Usare un CircuitHandler oggetto per acquisire un utente da AuthenticationStateProvider e impostare l'utente in un servizio. Se si vuole aggiornare l'utente, registrare un callback in AuthenticationStateChanged e accodare un Task oggetto per ottenere il nuovo utente e aggiornare il servizio. Nell'esempio seguente viene illustrato l'approccio .
Nell'esempio seguente :
- OnConnectionUpAsync viene chiamato ogni volta che il circuito si riconnette, impostando l'utente per la durata della connessione. È necessario solo il OnConnectionUpAsync metodo, a meno che non si implementino gli aggiornamenti tramite un gestore per le modifiche di autenticazione (
AuthenticationChanged
nell'esempio seguente). - OnCircuitOpenedAsync viene chiamato per collegare il gestore di autenticazione modificato,
AuthenticationChanged
, per aggiornare l'utente. - Il
catch
blocco dell'attivitàUpdateAuthentication
non esegue alcuna azione sulle eccezioni perché non è possibile segnalare le eccezioni a questo punto nell'esecuzione del codice. Se viene generata un'eccezione dall'attività, l'eccezione viene segnalata altrove nell'app.
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;
}
}
Nel file Program
:
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;
...
builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());
In Startup.ConfigureServices
di Startup.cs
:
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;
...
services.AddScoped<UserService>();
services.TryAddEnumerable(
ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());
Usare il servizio in un componente per ottenere l'utente:
@inject UserService UserService
<h1>Hello, @(UserService.GetUser().Identity?.Name ?? "world")!</h1>
Per impostare l'utente nel middleware per MVC, Razor Pages e in altri scenari di ASP.NET Core, chiamare SetUser
UserService
in middleware personalizzato dopo l'esecuzione del middleware di autenticazione o impostare l'utente con un'implementazione IClaimsTransformation . Nell'esempio seguente viene adottato l'approccio middleware.
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);
}
}
Immediatamente prima della chiamata a app.MapRazorComponents<App>()
nel Program
file, chiamare il middleware:
Immediatamente prima della chiamata a app.MapBlazorHub()
nel Program
file, chiamare il middleware:
Immediatamente prima della chiamata a app.MapBlazorHub()
in Startup.Configure
di Startup.cs
, chiamare il middleware:
app.UseMiddleware<UserServiceMiddleware>();
Accesso AuthenticationStateProvider
nel middleware delle richieste in uscita
È AuthenticationStateProvider possibile accedere a da un oggetto DelegatingHandler per HttpClient creato con IHttpClientFactory nel middleware delle richieste in uscita usando un gestore di attività del circuito.
Nota
Per indicazioni generali sulla definizione dei gestori di delega per le richieste HTTP per le istanze HttpClient create usando IHttpClientFactory nelle app ASP.NET Core, vedere le sezioni seguenti di Effettuare richieste HTTP usando IHttpClientFactory in ASP.NET Core:
Nell'esempio seguente viene AuthenticationStateProvider usato per associare un'intestazione di nome utente personalizzata per gli utenti autenticati alle richieste in uscita.
Implementare prima di tutto la classe nella sezione seguente dell'articolo inserimento delle dipendenze :First, implement the CircuitServicesAccessor
class in the following section of the Blazor dependency injection (DI) article:
Accedere ai servizi lato Blazor server da un ambito di inserimento delle dipendenze diverso
CircuitServicesAccessor
Usare per accedere all'oggetto AuthenticationStateProvider nell'implementazioneDelegatingHandler.
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
Nel file registrare AuthenticationStateHandler
e aggiungere il gestore all'oggetto IHttpClientFactory che crea HttpClient istanze:
builder.Services.AddTransient<AuthenticationStateHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<AuthenticationStateHandler>();