Udostępnij za pośrednictwem


ASP.NET Core i dodatkowe scenariusze zabezpieczeń po stronie serwera Blazor Web App

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla platformy .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla 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 dla 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 wykorzystują typy odwołań dopuszczających wartość null (NRT) oraz statyczną analizę stanu null kompilatora platformy .NET, które są obsługiwane w ASP.NET Core na platformie .NET 6 lub nowszej. W przypadku określania wartości docelowej dla platformy .NET 5 lub starszej usuń oznaczenie typu null (?) z typów string?, TodoItem[]?, WeatherForecast[]? i IEnumerable<GitHubBranch>? w przykładach artykułu.

Przekaż tokeny do aplikacji po stronie Blazor serwera

Ta sekcja dotyczy Blazor Web App. Aby uzyskać Blazor Server, zobacz wersję tej sekcji artykułu dla platformy .NET 7.

Jeśli chcesz tylko używać tokenów dostępu do wykonywania wywołań internetowego interfejsu API z Blazor Web Appnazwanego klienta HTTP, zobacz sekcję Używanie obsługi tokenów dla wywołań internetowego interfejsu API, w której wyjaśniono, jak używać DelegatingHandler implementacji w celu dołączenia tokenu dostępu użytkownika do żądań wychodzących. Poniższe wskazówki w tej sekcji dotyczą deweloperów, którzy potrzebują tokenów dostępu, tokenów odświeżania i innych właściwości uwierzytelniania po stronie serwera w innych celach.

Aby zapisać tokeny i inne właściwości uwierzytelniania na potrzeby użycia po stronie serwera w programie Blazor Web Apps, zalecamy użycie polecenia IHttpContextAccessor/HttpContext (IHttpContextAccessor, HttpContext). Odczytywanie tokenów z HttpContextprogramu , w tym jako parametr kaskadowy, przy użyciu IHttpContextAccessor jest obsługiwane w celu uzyskania tokenów do użycia podczas renderowania serwera interaktywnego, jeśli tokeny są uzyskiwane podczas renderowania statycznego po stronie serwera (statyczne SSR) lub prerendering. Jednak tokeny nie są aktualizowane, jeśli użytkownik uwierzytelnia się po ustanowieniu obwodu, ponieważ HttpContext jest przechwytywany na początku połączenia SignalR. Ponadto użycie AsyncLocal<T> przez IHttpContextAccessor oznacza, że należy zachować ostrożność, aby nie utracić kontekstu wykonywania przed odczytaniem HttpContext. Aby uzyskać więcej informacji, zobacz IHttpContextAccessor/HttpContext w aplikacjach ASP.NET Core Blazor.

W klasie usługi uzyskaj dostęp do składowych przestrzeni nazw Microsoft.AspNetCore.Authentication, aby udostępnić metodę GetTokenAsync na HttpContext. Alternatywną metodą, która jest wykomentowana w poniższym przykładzie, jest wywołanie AuthenticateAsync na HttpContext. Dla zwróconego AuthenticateResult.Properties wywołaj 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;
    }
}

Usługa jest zarejestrowana w pliku Program projektu serwera.

builder.Services.AddScoped<AuthenticationProcessor>();

AuthenticationProcessor można wstrzyknąć do usług po stronie serwera, na przykład we wstępnie DelegatingHandler skonfigurowanym HttpClient. Poniższy przykład jest przeznaczony tylko do celów demonstracyjnych lub w przypadku konieczności wykonania specjalnego przetwarzania w AuthenticationProcessor usłudze, ponieważ można po prostu wstrzyknąć IHttpContextAccessor i uzyskać token bezpośrednio do wywoływania zewnętrznych internetowych interfejsów API (aby uzyskać więcej informacji na temat bezpośredniego IHttpContextAccessor wywoływania internetowych interfejsów API, zobacz sekcję Use a token handler for web API calls (Używanie procedury obsługi tokenów dla wywołań internetowego interfejsu 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);
    }
}

Procedura obsługi tokenów jest zarejestrowana i działa jako program obsługujący delegowanie dla nazwanego klienta HTTP w Program pliku:

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>();

Ostrzeżenie

Upewnij się, że tokeny nigdy nie są przesyłane i obsługiwane przez klienta (projekt .Client), na przykład w składniku, który przyjmuje renderowanie interaktywne typu Auto i jest renderowany na kliencie lub przez usługę po stronie klienta. Zawsze klient wywołuje serwer (projekt) w celu przetwarzania żądań przy użyciu tokenów. Tokeny i inne dane uwierzytelniania nigdy nie powinny opuszczać serwera.

W przypadku interaktywnych składników aplikacji, zobacz Uwierzytelnianie i autoryzacja w ASP.NET CoreBlazor, który pokazuje, jak pozostawić tokeny dostępu i inne właściwości uwierzytelniania na serwerze. Należy również rozważyć wdrożenie wzorca Backend-for-Frontend (BFF), który przyjmuje podobną strukturę wywołań i jest opisany w temacie Zabezpieczanie aplikacji ASP.NET Core Blazor Web App za pomocą OpenID Connect (OIDC) dla dostawców OIDC oraz Zabezpieczanie aplikacji ASP.NET Core Blazor Web App za pomocą Microsoft Entra ID dla platformy internetowej Microsoft Identity z Entra.

Używanie mechanizmu obsługi tokenów na potrzeby wywołań API sieciowego

Poniższe podejście ma na celu dołączenie tokenu dostępu użytkownika do żądań wychodzących, w szczególności do wywołań API do zewnętrznych aplikacji. Podejście jest wyświetlane dla elementu Blazor Web App, który przyjmuje globalne renderowanie interaktywnego serwera, ale to samo ogólne podejście ma zastosowanie do Blazor Web Apps, które przyjmują globalny tryb automatycznego renderowania interaktywnego. Ważne jest, by pamiętać, że uzyskiwanie dostępu do HttpContext za pomocą IHttpContextAccessor jest wykonywane tylko na serwerze.

Aby zapoznać się ze wskazówkami w tej sekcji, zobacz BlazorWebAppOidc i BlazorWebAppOidcServer przykładowe aplikacje (.NET 8 lub nowsza) w Blazor repozytorium GitHub z przykładami. Przykłady przyjmują globalny tryb renderowania interaktywnego i uwierzytelnianie OIDC w firmie Microsoft Entra bez używania pakietów specyficznych dla firmy Entra. W przykładach pokazano, jak przekazać token dostępu JWT w celu wywołania bezpiecznego internetowego interfejsu API.

Platforma tożsamości firmy Microsoft z pakietami sieci Web firmy Microsoft dla identyfikatora Identity firmy Microsoft udostępnia interfejs API do wywoływania internetowych interfejsów API z poziomu Blazor Web Apps z automatycznym zarządzaniem tokenami i odnawianiem. Aby uzyskać więcej informacji, zobacz Zabezpieczanie ASP.NET Core Blazor Web App przy użyciu identyfikatora Entra firmy Microsoft oraz BlazorWebAppEntraBlazorWebAppEntraBff przykładowych aplikacji (.NET 9 lub nowszych) w Blazor repozytorium GitHub przykładów.

Podklasa DelegatingHandler do dołączania tokenu dostępu użytkownika do żądań wychodzących. Procedura obsługi tokenów jest wykonywana tylko na serwerze, więc użycie HttpContext jest bezpieczne.

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);
    }
}

Uwaga

Aby uzyskać wskazówki dotyczące uzyskiwania dostępu do elementu AuthenticationStateProvider z elementu DelegatingHandler, zobacz sekcję Dostęp AuthenticationStateProvider w programie pośredniczącym żądań wychodzących .

W pliku projektu Program program obsługi tokenów (TokenHandler) jest zarejestrowany jako usługa o określonym zakresie i określony jako program obsługi komunikatów klienta HTTP za pomocą polecenia AddHttpMessageHandler.

W poniższym przykładzie, {HTTP CLIENT NAME} to nazwa HttpClient, a {BASE ADDRESS} to podstawowy adres URI internetowego interfejsu API. Aby uzyskać więcej informacji na temat AddHttpContextAccessor, zobacz IHttpContextAccessor/HttpContext w aplikacjach ASP.NET CoreBlazor.

W pliku Program.cs:

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<TokenHandler>();

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

Przykład:

builder.Services.AddScoped<TokenHandler>();

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

Podstawowy adres klienta HTTP można podać z konfiguracji za pomocą polecenia builder.Configuration["{CONFIGURATION KEY}"], gdzie symbol {CONFIGURATION KEY} zastępczy jest kluczem konfiguracji:

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

W appsettings.json określ ExternalApiUri. Poniższy przykład ustawia wartość na adres localhost dla zewnętrznego internetowego interfejsu API: https://localhost:7277

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

Na tym etapie HttpClient utworzony przez składnik może wysyłać bezpieczne żądania do interfejsu API. W poniższym przykładzie {REQUEST URI} parametr to względny identyfikator URI żądania, a {HTTP CLIENT NAME} symbol zastępczy to nazwa elementu HttpClient:

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

Przykład:

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

Dodatkowe funkcje są planowane dla Blazor, które są śledzone przez Access AuthenticationStateProvider w midware żądań wychodzących (dotnet/aspnetcore #52379). Problem z udostępnianiem tokenu dostępu do klienta HttpClient w trybie serwera interaktywnego (dotnet/aspnetcore #52390) to zamknięty problem, który zawiera pomocną dyskusję i potencjalne strategie obejścia dla zaawansowanych przypadków użycia.

Tokeny dostępne poza komponentami aplikacji po stronie serwera można przekazać do komponentów przy użyciu podejścia opisanego w tej sekcji. W przykładzie w tej sekcji skoncentrowano się na przekazywaniu tokenów dostępu, odświeżania i ochrony przed żądaniami (XSRF) do aplikacji , ale podejście jest prawidłowe dla innych stanów kontekstu HTTP.

Uwaga

Przekazywanie tokenu XSRF do Razor komponentów jest przydatne w scenariuszach, w których komponenty wysyłają żądania 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. Przygotuj 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 za pomocą options.Scope.Add("{SCOPE}");, gdzie {SCOPE} jako symbol zastępczy jest dodatkowym zakresem do dodania.

Zdefiniuj usługę dostawcy tokenów o zasięgu ograniczonym, która może być używana w Blazor aplikacji do rozwiązywania tokenów przy użyciu 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ę, aby przekazać początkowy stan aplikacji z tokenami 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; }
}

Utwórz w pliku instancję Pages/_Host.cshtml i przekaż ją jako parametr do aplikacji:

Utwórz w pliku instancję Pages/_Layout.cshtml i przekaż ją jako parametr do aplikacji:

Utwórz w pliku instancję Pages/_Host.cshtml i przekaż ją 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 NuGet w aplikacji.

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 do API, należy zintegrować dostawcę tokenów i pobrać token dla tego żądania.

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>();
    }
}

Aby przekazać token XSRF do składnika, wstrzyknij TokenProvider i dodaj token XSRF do żądania POST. Poniższy przykład dodaje token do punktu końcowego wylogowania POST. Scenariusz poniższego przykładu zakłada, że punkt końcowy wylogowania (Areas/Identity/Pages/Account/Logout.cshtml, osadzony w aplikacji) nie określa IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]), ponieważ oprócz standardowej operacji wylogowania wykonuje dodatkowe działanie, które musi być chronione. Punkt końcowy wymaga prawidłowego tokenu XSRF, aby pomyślnie przetworzyć żądanie.

W składniku, który wyświetla 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.csprogramu . 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.Configureprogramu . 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ż .NET 5 biblioteka uwierzytelniania i Blazor szablony używają punktów końcowych OpenID Connect (OIDC) wersji 1.0. Aby użyć punktu końcowego v2.0 z wersjami platformy ASP.NET Core przed wersją .NET 5, 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 dołączenie segmentu do autorytetu nie jest odpowiednie dla dostawcy OIDC aplikacji, na przykład z dostawcami innymi niż ME-ID, ustaw właściwość Authority bezpośrednio. Ustaw właściwość w OpenIdConnectOptions lub w pliku ustawień aplikacji za pomocą klucza Authority.

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 roszczeń w tokenie identyfikatora są dostępne w referencji roszczeń tokenu ID.

  • Ponieważ zasoby są określone w identyfikatorach URI zakresu dla punktów końcowych v2.0, usuń właściwość OpenIdConnectOptions.Resource w OpenIdConnectOptions:

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

Identyfikator URI aplikacji

  • W przypadku korzystania z punktów końcowych w wersji 2.0 interfejsy API definiują element App ID URI, który reprezentuje unikatowy identyfikator interfejsu API.
  • Wszystkie zakresy zawierają URI identyfikatora aplikacji jako prefiks, a punkty końcowe w wersji 2.0 emitują tokeny dostępu przy użyciu URI identyfikatora aplikacji jako odbiorcy.
  • W przypadku korzystania z punktów końcowych V2.0 identyfikator klienta skonfigurowany w interfejsie API serwera zmienia się z identyfikatora aplikacji interfejsu API (identyfikator klienta) na URI identyfikatora aplikacji.

appsettings.json:

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

Identyfikator URI aplikacji, którego należy użyć, można znaleźć w opisie rejestracji aplikacji dostawcy OIDC.

Moduł obsługi do pozyskiwania 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 do AuthenticationStateChanged oraz umieść zadanie Task w kolejce, 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 połączenie zostaje ponownie nawiązane, ustawiając użytkownika na czas trwania połączenia. Tylko metoda OnConnectionUpAsync jest wymagana, chyba że zaimplementujesz aktualizacje za pośrednictwem programu obsługi zmian w uwierzytelnianiu (AuthenticationChanged w poniższym przykładzie).
  • OnCircuitOpenedAsync jest wywoływana w celu dołączenia procedury obsługi zmiany uwierzytelniania, AuthenticationChanged, w celu zaktualizowania użytkownika.
  • catch Blok UpdateAuthentication 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 występuje w zadaniu, jest zgłaszany gdzie indziej 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 komponencie, aby uzyskać użytkownika.

@inject UserService UserService

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

Aby ustawić użytkownika w oprogramowaniu pośredniczącym dla MVC, Razor stron i w innych scenariuszach ASP.NET Core, wywołaj SetUser na UserService w niestandardowym oprogramowaniu pośredniczącym 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>() w pliku Program wywołaj oprogramowanie pośredniczące:

Bezpośrednio przed wywołaniem app.MapBlazorHub() w pliku Program wywołaj oprogramowanie pośredniczące:

Bezpośrednio przed wywołaniem metody app.MapBlazorHub() w Startup.Configure programie Startup.cswywołaj oprogramowanie pośredniczące:

app.UseMiddleware<UserServiceMiddleware>();

Dostęp AuthenticationStateProvider do oprogramowania pośredniczącego żądań wychodzących

Dostęp AuthenticationStateProvider z DelegatingHandler dla HttpClient utworzonego za pomocą IHttpClientFactory można uzyskać w pośredniczącym przetwarzaniu żądań wychodzących, korzystając z obsługi działań obwodu.

Uwaga

Aby uzyskać ogólne wskazówki dotyczące określania delegujących procedur obsługi żą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 IHttpClientFactory w ASP.NET Core):

W poniższym przykładzie użyto AuthenticationStateProvider do dołączania niestandardowego nagłówka z nazwą 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):

Blazor

Użyj CircuitServicesAccessor, aby uzyskać dostęp do AuthenticationStateProvider w implementacji 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);
    }
}

W pliku Program zarejestruj AuthenticationStateHandler i dodaj obsługę do IHttpClientFactory, który tworzy wystąpienia HttpClient:

builder.Services.AddTransient<AuthenticationStateHandler>();

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