Udostępnij za pomocą


ASP.NET Core Blazor WebAssembly dodatkowe scenariusze zabezpieczeń

Note

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.

Warning

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 .NET 9.

W tym artykule opisano dodatkowe scenariusze zabezpieczeń dla Blazor WebAssembly aplikacji.

Dołączanie tokenów do żądań wychodzących

AuthorizationMessageHandler jest używany do przetwarzania DelegatingHandler tokenów dostępu. Tokeny są uzyskiwane przy użyciu IAccessTokenProvider usługi, która jest zarejestrowana przez platformę. Jeśli nie można uzyskać tokenu, zgłaszany jest AccessTokenNotAvailableException. AccessTokenNotAvailableException ma metodę Redirect, która nawiguje do AccessTokenResult.InteractiveRequestUrl przy użyciu podanej AccessTokenResult.InteractionOptions, aby umożliwić odświeżenie tokenu dostępu.

Dla wygody platforma udostępnia BaseAddressAuthorizationMessageHandler wstępnie skonfigurowany adres podstawowy aplikacji jako autoryzowany adres URL. Tokeny dostępu są dodawane tylko wtedy, gdy identyfikator URI żądania znajduje się w podstawowym identyfikatorze URI aplikacji. Jeśli identyfikatory URI żądań wychodzących nie należą do podstawowego identyfikatora URI aplikacji, użyj klasy niestandardowej AuthorizationMessageHandler (zalecanej) lub skonfiguruj element AuthorizationMessageHandler.

Note

Oprócz konfiguracji aplikacji klienckiej na potrzeby dostępu do interfejsu API serwera interfejs API serwera musi również zezwalać na żądania między źródłami (CORS), gdy klient i serwer nie znajdują się pod tym samym adresem podstawowym. Aby uzyskać więcej informacji na temat konfiguracji mechanizmu CORS po stronie serwera, zobacz sekcję Współużytkowanie zasobów między źródłami (CORS) w dalszej części tego artykułu.

W poniższym przykładzie:

W poniższym przykładzie HttpClientFactoryServiceCollectionExtensions.AddHttpClient jest rozszerzeniem w pliku Microsoft.Extensions.Http. Dodaj pakiet do aplikacji, która jeszcze nie ma do niego odniesienia.

Note

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.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient("WebAPI", 
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("WebAPI"));

W przypadku Blazorrozwiązania hostowanego opartego na Blazor WebAssembly szablonie projektu, identyfikatory URI żądań znajdują się w podstawowym identyfikatorze URI aplikacji. IWebAssemblyHostEnvironment.BaseAddress jest przypisywany do new Uri(builder.HostEnvironment.BaseAddress) w aplikacji wygenerowanej na podstawie szablonu projektu.

Skonfigurowany HttpClient jest używany do tworzenia autoryzowanych żądań przy użyciu try-catch wzorca:

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()
{
    try
    {
        var examples = 
            await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

        ...
    }
    catch (AccessTokenNotAvailableException exception)
    {
        exception.Redirect();
    }
}

Scenariusze żądań uwierzytelniania niestandardowego

W poniższych scenariuszach pokazano, jak dostosować żądania uwierzytelniania i jak uzyskać ścieżkę logowania z opcji uwierzytelniania.

Dostosowywanie procesu logowania

Zarządzaj dodatkowymi parametrami żądania logowania, używając następujących metod jeden lub więcej razy na nowym wystąpieniu programu InteractiveRequestOptions.

W poniższym LoginDisplay przykładzie składnika dodatkowe parametry są dodawane do żądania logowania:

  • prompt jest ustawiona na loginwartość : Wymusza, aby użytkownik wprowadzał swoje poświadczenia na tym żądaniu, negując logowanie jednokrotne.
  • loginHint jest ustawiona na peter@contoso.comwartość : Wstępnie wypełnia pole nazwy użytkownika/adresu e-mail strony logowania dla użytkownika na wartość peter@contoso.com. Aplikacje często używają tego parametru podczas ponownego uwierzytelniania, po wcześniejszym wyodrębnieniu nazwy użytkownika z wcześniejszego logowania przy użyciu roszczenia preferred_username.

Shared/LoginDisplay.razor:

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity?.Name!
        <button @onclick="BeginLogOut">Log out</button>
    </Authorized>
    <NotAuthorized>
        <button @onclick="BeginLogIn">Log in</button>
    </NotAuthorized>
</AuthorizeView>

@code{
    public void BeginLogOut()
    {
        Navigation.NavigateToLogout("authentication/logout");
    }

    public void BeginLogIn()
    {
        InteractiveRequestOptions requestOptions =
            new()
            {
                Interaction = InteractionType.SignIn,
                ReturnUrl = Navigation.Uri,
            };

        requestOptions.TryAddAdditionalParameter("prompt", "login");
        requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");

        Navigation.NavigateToLogin("authentication/login", requestOptions);
    }
}

Aby uzyskać więcej informacji, zobacz następujące zasoby:

Dostosowywanie opcji przed interakcyjnym uzyskaniem tokenu

W takim AccessTokenNotAvailableException przypadku można zarządzać dodatkowymi parametrami dla nowego żądania tokenu dostępu dostawcy tożsamości przy użyciu następujących metod co najmniej raz w nowym wystąpieniu InteractiveRequestOptionsprogramu :

W poniższym przykładzie, który uzyskuje dane JSON za pośrednictwem internetowego interfejsu API, dodatkowe parametry są dodawane do żądania przekierowania, jeśli token dostępu nie jest obecny (AccessTokenNotAvailableException jest wywoływany):

  • prompt jest ustawiona na loginwartość : Wymusza, aby użytkownik wprowadzał swoje poświadczenia na tym żądaniu, negując logowanie jednokrotne.
  • loginHint jest ustawiona na peter@contoso.comwartość : Wstępnie wypełnia pole nazwy użytkownika/adresu e-mail strony logowania dla użytkownika na wartość peter@contoso.com. Aplikacje często używają tego parametru podczas ponownego uwierzytelniania, po wcześniejszym wyodrębnieniu nazwy użytkownika z wcześniejszego logowania przy użyciu roszczenia preferred_username.
try
{
    var examples = await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

    ...
}
catch (AccessTokenNotAvailableException ex)
{
    ex.Redirect(requestOptions => {
        requestOptions.TryAddAdditionalParameter("prompt", "login");
        requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");
    });
}

W poprzednim przykładzie przyjęto założenie, że:

Aby uzyskać więcej informacji, zobacz następujące zasoby:

Dostosowywanie opcji podczas korzystania z elementu IAccessTokenProvider

Jeśli uzyskanie tokenu zakończy się niepowodzeniem podczas korzystania z IAccessTokenProvider, zarządzaj dodatkowymi parametrami dla nowego żądania tokenu dostępu dostawcy tożsamości, używając następujących metod jeden lub więcej razy w nowym wystąpieniu InteractiveRequestOptions.

W poniższym przykładzie, który próbuje uzyskać token dostępu dla użytkownika, dodatkowe parametry są dodawane do żądania logowania, jeśli próba uzyskania tokenu zakończy się niepowodzeniem po TryGetToken wywołaniu:

  • prompt jest ustawiona na loginwartość : Wymusza, aby użytkownik wprowadzał swoje poświadczenia na tym żądaniu, negując logowanie jednokrotne.
  • loginHint jest ustawiona na peter@contoso.comwartość : Wstępnie wypełnia pole nazwy użytkownika/adresu e-mail strony logowania dla użytkownika na wartość peter@contoso.com. Aplikacje często używają tego parametru podczas ponownego uwierzytelniania, po wcześniejszym wyodrębnieniu nazwy użytkownika z wcześniejszego logowania przy użyciu roszczenia preferred_username.
var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = [ ... ]
    });

if (!tokenResult.TryGetToken(out var token))
{
    tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt", "login");
    tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint", 
        "peter@contoso.com");

    Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl, 
        accessTokenResult.InteractionOptions);
}
var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { ... }
    });

if (!tokenResult.TryGetToken(out var token))
{
    tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt", "login");
    tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint", 
        "peter@contoso.com");

    Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl, 
        accessTokenResult.InteractionOptions);
}

W poprzednim przykładzie przyjęto założenie:

Aby uzyskać więcej informacji, zobacz następujące zasoby:

Wylogowywanie przy użyciu niestandardowego zwrotnego adresu URL

Poniższy przykład wyloguje użytkownika i zwraca użytkownika do punktu końcowego /goodbye :

Navigation.NavigateToLogout("authentication/logout", "goodbye");

Uzyskiwanie ścieżki logowania z opcji uwierzytelniania

Uzyskaj skonfigurowaną ścieżkę logowania z witryny RemoteAuthenticationOptions:

var loginPath = 
    RemoteAuthOptions.Get(Options.DefaultName).AuthenticationPaths.LogInPath;

W poprzednim przykładzie przyjęto założenie:

Klasa niestandardowa AuthorizationMessageHandler

Wskazówki w tej sekcji są zalecane dla aplikacji klienckich, które wysyłają żądania wychodzące do URI spoza podstawowego URI aplikacji.

W poniższym przykładzie niestandardowa klasa rozszerza AuthorizationMessageHandler do użycia jako DelegatingHandler dla HttpClient. ConfigureHandler Konfiguruje tę procedurę obsługi w celu autoryzowania wychodzących żądań HTTP przy użyciu tokenu dostępu. Token dostępu jest dołączany tylko wtedy, gdy co najmniej jeden z autoryzowanych adresów URL jest bazą identyfikatora URI żądania (HttpRequestMessage.RequestUri).

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, 
        NavigationManager navigation)
        : base(provider, navigation)
    {
        ConfigureHandler(
            authorizedUrls: [ "https://api.contoso.com/v1.0" ],
            scopes: [ "example.read", "example.write" ]);
    }
}

Note

W tej sekcji poprzednia procedura obsługi komunikatów jest używana podczas tworzenia skonfigurowanego HttpClient na podstawie wprowadzonego IHttpClientFactoryelementu . Jeśli nie używasz IHttpClientFactoryklasy, musisz utworzyć HttpClientHandler wystąpienie i przypisać je do AuthorizationMessageHandler's DelegatingHandler.InnerHandler.

InnerHandler = new HttpClientHandler();

Nie musisz wykonywać poprzedniego przypisania InnerHandler, jeśli używasz IHttpClientFactory metody, ponieważ wywołanie ExampleAPIMethod zostanie pokazane w dalszej części tej sekcji.

W poprzednim kodzie zakresy example.read i example.write są ogólnymi przykładami, które nie mają odzwierciedlać prawidłowych zakresów dla żadnego konkretnego dostawcy.

W pliku ProgramCustomAuthorizationMessageHandler jest rejestrowany jako usługa tymczasowa i jest skonfigurowany jako DelegatingHandler dla wychodzących wystąpień HttpResponseMessage, utworzonych przez nazwany HttpClient.

W poniższym przykładzie HttpClientFactoryServiceCollectionExtensions.AddHttpClient jest rozszerzeniem w pliku Microsoft.Extensions.Http. Dodaj pakiet do aplikacji, która jeszcze nie ma do niego odniesienia.

Note

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.

builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("WebAPI",
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

Note

W poprzednim przykładzie parametr CustomAuthorizationMessageHandlerDelegatingHandler jest zarejestrowany jako usługa przejściowa dla elementu AddHttpMessageHandler. Rejestracja przejściowa jest zalecana w przypadku IHttpClientFactory, który zarządza własnymi zakresami iniekcji zależności. Aby uzyskać więcej informacji, zobacz następujące zasoby:

W przypadku hostowanego rozwiązania opartego na szablonie projektu Blazor, Blazor WebAssembly () jest przypisany do IWebAssemblyHostEnvironment.BaseAddress.

Skonfigurowany HttpClient jest używany do tworzenia autoryzowanych żądań przy użyciu try-catch wzorca. Gdy klient jest tworzony za pomocą CreateClient (Microsoft.Extensions.Http pakietu), HttpClient otrzymuje wystąpienia zawierające tokeny dostępu podczas wysyłania żądań do interfejsu API serwera. Jeśli identyfikator URI żądania jest względny, tak jak w poniższym przykładzie (ExampleAPIMethod), zostaje połączony z BaseAddress po wysłaniu żądania przez aplikację kliencką.

@inject IHttpClientFactory ClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("WebAPI");

            var examples = 
                await client.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

            ...
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Skonfiguruj AuthorizationMessageHandler

AuthorizationMessageHandler można skonfigurować, korzystając z autoryzowanych adresów URL, zakresów oraz zwrotnego adresu URL, za pomocą metody ConfigureHandler. ConfigureHandler Konfiguruje program obsługi w celu autoryzowania wychodzących żądań HTTP przy użyciu tokenu dostępu. Token dostępu jest dołączany tylko wtedy, gdy co najmniej jeden z autoryzowanych adresów URL jest bazą identyfikatora URI żądania (HttpRequestMessage.RequestUri). Jeśli identyfikator URI żądania jest względnym identyfikatorem URI, jest połączony z elementem BaseAddress.

W poniższym przykładzie AuthorizationMessageHandler konfiguruje element HttpClient w pliku Program.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(
    sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: [ "https://api.contoso.com/v1.0" ],
        scopes: [ "example.read", "example.write" ])
    .InnerHandler = new HttpClientHandler())
{
    BaseAddress = new Uri("https://api.contoso.com/v1.0")
});

W poprzednim kodzie:

W przypadku hostowanego Blazor rozwiązania opartego na szablonie projektu Blazor WebAssembly, IWebAssemblyHostEnvironment.BaseAddress jest przypisany do następujących:

Wpisane HttpClient

Typizowanego klienta można zdefiniować, który obsługuje wszystkie problemy z pozyskiwaniem protokołu HTTP i tokenu w ramach jednej klasy.

WeatherForecastClient.cs:

using System.Net.Http.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {PACKAGE ID/ASSEMBLY NAME}.Data;

public class WeatherForecastClient(HttpClient http)
{
    private WeatherForecast[]? forecasts;

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts ?? Array.Empty<WeatherForecast>();
    }
}

W poprzednim przykładzie WeatherForecast typ jest klasą statyczną, która przechowuje dane prognozy pogody. Symbol zastępczy {PACKAGE ID/ASSEMBLY NAME} to identyfikator pakietu projektu (<PackageId> w pliku projektu) używany dla nazwy biblioteki lub zestawu aplikacji (na przykład using static BlazorSample.Data;).

W poniższym przykładzie HttpClientFactoryServiceCollectionExtensions.AddHttpClient jest rozszerzeniem w pliku Microsoft.Extensions.Http. Dodaj pakiet do aplikacji, która jeszcze nie ma do niego odniesienia.

Note

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 pliku Program:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

W przypadku hostowanego rozwiązania opartego na szablonie projektu Blazor, Blazor WebAssembly () jest przypisany do IWebAssemblyHostEnvironment.BaseAddress.

W składniku, który pobiera dane pogodowe:

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()
{
    forecasts = await Client.GetForecastAsync();
}

HttpClient Konfigurowanie programu obsługi

Program obsługujący można dodatkowo skonfigurować ConfigureHandler dla wychodzących żądań HTTP.

W poniższym przykładzie HttpClientFactoryServiceCollectionExtensions.AddHttpClient jest rozszerzeniem w pliku Microsoft.Extensions.Http. Dodaj pakiet do aplikacji, która jeszcze nie ma do niego odniesienia.

Note

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 pliku Program:

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: [ "https://api.contoso.com/v1.0" ],
        scopes: [ "example.read", "example.write" ]));
builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new[] { "https://api.contoso.com/v1.0" },
        scopes: new[] { "example.read", "example.write" }));

W poprzednim kodzie zakresy example.read i example.write są ogólnymi przykładami, które nie mają odzwierciedlać prawidłowych zakresów dla żadnego konkretnego dostawcy.

W przypadku hostowanego Blazor rozwiązania opartego na szablonie projektu Blazor WebAssembly, IWebAssemblyHostEnvironment.BaseAddress jest przypisany do następujących:

Nieuwierzytelnione lub nieautoryzowane żądania internetowego interfejsu API w aplikacji z bezpiecznym klientem domyślnym

Aplikacja, która zwykle używa bezpiecznego ustawienia domyślnego HttpClient , może również tworzyć nieuwierzytelnione lub nieautoryzowane żądania internetowego interfejsu API, konfigurując nazwę HttpClient.

W poniższym przykładzie HttpClientFactoryServiceCollectionExtensions.AddHttpClient jest rozszerzeniem w pliku Microsoft.Extensions.Http. Dodaj pakiet do aplikacji, która jeszcze nie ma do niego odniesienia.

Note

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 pliku Program:

builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient", 
    client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"));

W przypadku hostowanego rozwiązania opartego na szablonie projektu Blazor, Blazor WebAssembly () jest przypisany do IWebAssemblyHostEnvironment.BaseAddress.

Poprzednia rejestracja jest dodatkiem do istniejącej bezpiecznej rejestracji domyślnej HttpClient .

Składnik tworzy element HttpClient z IHttpClientFactory (Microsoft.Extensions.Http pakietu) w celu tworzenia nieuwierzytelnionych lub nieautoryzowanych żądań:

@inject IHttpClientFactory ClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        var client = ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

        var examples = await client.GetFromJsonAsync<ExampleType[]>(
            "ExampleNoAuthentication");

        ...
    }
}

Note

Kontroler w interfejsie API serwera, ExampleNoAuthenticationController dla poprzedniego przykładu, nie jest oznaczony atrybutem [Authorize].

Decyzja, czy używać bezpiecznego klienta jako domyślnego wystąpienia HttpClient, czy niezabezpieczonego klienta, należy do programisty. Jednym ze sposobów podejmowania tej decyzji jest rozważenie liczby uwierzytelnionych i nieuwierzytelnionych punktów końcowych, z którymi kontaktuje się aplikacja. Jeśli większość żądań aplikacji ma na celu ochronę punktów końcowych interfejsu API, użyj uwierzytelnionego wystąpienia HttpClient jako domyślnego. W przeciwnym razie zarejestruj nieuwierzytelnione HttpClient wystąpienie jako domyślne.

Alternatywną metodą korzystania z klasy IHttpClientFactory jest utworzenie typizowanego klienta w celu uzyskania nieuwierzytelnionego dostępu do anonimowych punktów końcowych.

Żądanie dodatkowych tokenów dostępu

Tokeny dostępu można uzyskać ręcznie, wywołując funkcję IAccessTokenProvider.RequestAccessToken. W poniższym przykładzie aplikacja wymaga dodatkowego zakresu dla ustawień domyślnych HttpClient. Przykład biblioteki Microsoft Authentication Library (MSAL) konfiguruje zakres za pomocą polecenia MsalProviderOptions:

W pliku Program:

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 1}");
    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 2}");
}

Pola zastępcze {CUSTOM SCOPE 1} i {CUSTOM SCOPE 2} w poprzednim przykładzie to zakresy niestandardowe.

Note

AdditionalScopesToConsent Nie jest możliwe przyznawanie delegowanych uprawnień użytkownika dla Microsoft Graph za pośrednictwem interfejsu zgody Microsoft Entra ID, gdy użytkownik po raz pierwszy korzysta z aplikacji zarejestrowanej na platformie Microsoft Azure. Aby uzyskać więcej informacji, zobacz Use Graph API with ASP.NET Core (Używanie interfejsu API programu Graph z programem ASP.NET Core Blazor WebAssembly).

Metoda IAccessTokenProvider.RequestAccessToken zapewnia przeciążenie, które umożliwia aplikacji uzyskanie tokenu dostępu dla określonego zestawu zakresów.

W składniku Razor :

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = [ "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" ]
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}

Pola zastępcze {CUSTOM SCOPE 1} i {CUSTOM SCOPE 2} w poprzednim przykładzie to zakresy niestandardowe.

AccessTokenResult.TryGetToken Zwraca:

  • true do użycia z token.
  • false jeśli token nie został pobrany.

Udostępnianie zasobów z różnych źródeł (CORS)

Podczas wysyłania poświadczeń (ciasteczek/nagłówków autoryzacji) w żądaniach CORS, nagłówek Authorization musi być dozwolony przez politykę CORS.

Następująca polityka obejmuje konfigurację dla:

  • Źródła żądań (http://localhost:5000, https://localhost:5001).
  • Dowolna metoda (czasownik).
  • Content-Type i Authorization nagłówki. Aby zezwolić na nagłówek niestandardowy (na przykład x-custom-header), wyświetl nagłówek podczas wywoływania elementu WithHeaders.
  • Poświadczenia ustawione przez kod JavaScript po stronie klienta (credentials właściwość ustawiona na include).
app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
        .AllowAnyMethod()
        .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, 
            "x-custom-header")
        .AllowCredentials());

Blazor Hostowane rozwiązanie oparte na szablonie Blazor WebAssembly projektu używa tego samego adresu podstawowego dla aplikacji klienckich i serwerowych. Aplikacja kliencka HttpClient.BaseAddress jest ustawiona na identyfikator URI builder.HostEnvironment.BaseAddress. Konfiguracja mechanizmu CORS nie jest wymagana w domyślnej konfiguracji hostowanego Blazor rozwiązania. Dodatkowe aplikacje klienckie, które nie są hostowane przez projekt serwera i nie współużytkują podstawowego adresu aplikacji serwera, wymagają konfiguracji mechanizmu CORS w projekcie serwera.

Aby uzyskać więcej informacji, zobacz Włączanie żądań między źródłami (CORS) w ASP.NET Core i składnik testera żądań HTTP w przykładowej aplikacji (Components/HTTPRequestTester.razor).

Obsługa błędów żądania tokenu

Gdy jednostronicowa aplikacja (SPA) uwierzytelnia użytkownika przy użyciu protokołu OpenID Connect (OIDC), stan uwierzytelniania jest utrzymywany lokalnie w SPA i w Identity dostawcy (IP) w postaci sesji cookie ustawionej w wyniku podania poświadczeń przez użytkownika.

Tokeny emitowane przez adres IP dla użytkownika zwykle są ważne przez krótki czas, czyli około jednej godziny, więc aplikacja kliencka musi regularnie pobierać nowe tokeny. W przeciwnym razie użytkownik zostanie wylogowany po wygaśnięciu przyznanych tokenów. W większości przypadków klienci OIDC mogą aprowizować nowe tokeny bez konieczności ponownego uwierzytelniania użytkownika dzięki stanowi uwierzytelniania lub "sesji", który jest przechowywany w adresie IP.

Istnieje kilka przypadków, w których klient nie może uzyskać tokenu bez interakcji użytkownika, na przykład, gdy z jakiegoś powodu użytkownik jawnie wyloguje się z adresu IP. Ten scenariusz występuje, jeśli użytkownik odwiedza https://login.microsoftonline.com i wyloguje się. W tych scenariuszach aplikacja nie wie natychmiast, że użytkownik wylogował się. Każdy token przechowywany przez klienta może już nie być prawidłowy. Ponadto klient nie może aprowizować nowego tokenu bez interakcji użytkownika po wygaśnięciu bieżącego tokenu.

Te scenariusze nie są specyficzne dla uwierzytelniania opartego na tokenach. Są one częścią charakteru SPA. SPA używające plików cookie również nie może wywołać interfejsu API serwera, jeśli uwierzytelnianie cookie zostanie usunięte.

Gdy aplikacja wykonuje wywołania interfejsu API do chronionych zasobów, należy pamiętać o następujących kwestiach:

  • Aby aprowizować nowy token dostępu w celu wywołania interfejsu API, użytkownik może być zobowiązany do ponownego uwierzytelnienia.
  • Nawet jeśli klient ma token, który wydaje się być prawidłowy, wywołanie serwera może zakończyć się niepowodzeniem, ponieważ token został odwołany przez użytkownika.

Gdy aplikacja żąda tokenu, istnieją dwa możliwe wyniki:

  • Żądanie zakończy się pomyślnie, a aplikacja ma prawidłowy token.
  • Żądanie kończy się niepowodzeniem, a aplikacja musi ponownie uwierzytelnić użytkownika, aby uzyskać nowy token.

Jeśli żądanie tokenu zakończy się niepowodzeniem, musisz zdecydować, czy chcesz zapisać bieżący stan przed wykonaniem przekierowania. Istnieje kilka podejść do przechowywania stanu z rosnącym poziomem złożoności:

  • Zapisz bieżący stan strony w przechowalni sesji. OnInitializedAsync Podczas metody cyklu życia (OnInitializedAsync) sprawdź, czy można przywrócić stan przed kontynuowaniem.
  • Dodaj parametr ciągu zapytania i użyj go jako sposobu, aby zasygnalizować aplikację, że musi ponownie nawodnić wcześniej zapisany stan.
  • Dodaj parametr ciągu zapytania z unikatowym identyfikatorem do przechowywania danych w magazynie sesji bez ryzyka kolizji z innymi elementami.

Zapisz stan aplikacji przed operacją uwierzytelniania za pomocą przestrzeni sesji

W poniższym przykładzie pokazano, jak:

  • Zachowaj stan przed przekierowaniem do strony logowania.
  • Odzyskaj poprzedni stan po uwierzytelnieniu przy użyciu parametru ciągu zapytania.
...
@using System.Text.Json
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject IJSRuntime JS
@inject NavigationManager Navigation

<EditForm Model="User" OnSubmit="OnSaveAsync">
    <label>
        First Name: 
        <InputText @bind-Value="User!.Name" />
    </label>
    <label>
        Last Name: 
        <InputText @bind-Value="User!.LastName" />
    </label>
    <button type="submit">Save User</button>
</EditForm>

@code {
    public Profile User { get; set; } = new Profile();

    protected override async Task OnInitializedAsync()
    {
        var currentQuery = new Uri(Navigation.Uri).Query;

        if (currentQuery.Contains("state=resumeSavingProfile"))
        {
            var user = await JS.InvokeAsync<string>("sessionStorage.getItem",
                "resumeSavingProfile");

            if (!string.IsNullOrEmpty(user))
            {
                User = JsonSerializer.Deserialize<Profile>(user);
            }
        }
    }

    public async Task OnSaveAsync()
    {
        var http = new HttpClient();
        http.BaseAddress = new Uri(Navigation.BaseUri);

        var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

        var tokenResult = await TokenProvider.RequestAccessToken(
            new AccessTokenRequestOptions
            {
                ReturnUrl = resumeUri
            });

        if (tokenResult.TryGetToken(out var token))
        {
            http.DefaultRequestHeaders.Add("Authorization", 
                $"Bearer {token.Value}");
            await http.PostAsJsonAsync("Save", User);
        }
        else
        {
            await JS.InvokeVoidAsync("sessionStorage.setItem", 
                "resumeSavingProfile", JsonSerializer.Serialize(User));
            Navigation.NavigateTo(tokenResult.InteractiveRequestUrl);
        }
    }

    public class Profile
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }
}

Zapisywanie stanu aplikacji przed operacją uwierzytelniania przy użyciu magazynu sesji i kontenera stanu

Podczas operacji uwierzytelniania istnieją przypadki, w których chcesz zapisać stan aplikacji przed przekierowanie przeglądarki do adresu IP. Może to być przypadek, gdy używasz kontenera stanu i chcesz przywrócić stan po pomyślnym uwierzytelnieniu. Możesz użyć niestandardowego obiektu stanu uwierzytelniania, aby zachować stan specyficzny dla aplikacji lub odwołanie do niego i przywrócić ten stan po pomyślnym zakończeniu operacji uwierzytelniania. W poniższym przykładzie pokazano podejście.

Klasa kontenera stanu jest tworzona w aplikacji z właściwościami do przechowywania wartości stanu aplikacji. W poniższym przykładzie kontener jest używany do obsługi wartości licznika składnika domyślnego Blazor projektu (). Metody oparte na System.Text.Json dotyczą serializacji i deserializacji kontenera.

using System.Text.Json;

public class StateContainer
{
    public int CounterValue { get; set; }

    public string GetStateForLocalStorage() => JsonSerializer.Serialize(this);

    public void SetStateFromLocalStorage(string locallyStoredState)
    {
        var deserializedState = 
            JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

        CounterValue = deserializedState.CounterValue;
    }
}

Składnik Counter używa kontenera stanu do utrzymania currentCount wartości poza składnikiem.

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override void OnInitialized()
    {
        if (State.CounterValue > 0)
        {
            currentCount = State.CounterValue;
        }
    }

    private void IncrementCount()
    {
        currentCount++;
        State.CounterValue = currentCount;
    }
}

Utwórz element ApplicationAuthenticationState na podstawie RemoteAuthenticationState. Id Podaj właściwość, która służy jako identyfikator stanu przechowywanego lokalnie.

ApplicationAuthenticationState.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string? Id { get; set; }
}

Składnik Authentication (Authentication.razor) zapisuje i przywraca stan aplikacji przy użyciu lokalnego magazynu sesji z metodami serializacji i deserializacji, StateContainer i GetStateForLocalStorage: SetStateFromLocalStorage

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IJSRuntime JS
@inject StateContainer State

<RemoteAuthenticatorViewCore Action="Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

@code {
    [Parameter]
    public string? Action { get; set; }

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}

W tym przykładzie do uwierzytelniania używa się Microsoft Entra (ME-ID). W pliku Program:

  • Parametr ApplicationAuthenticationState jest skonfigurowany jako typ biblioteki Microsoft Authentication Library (MSAL RemoteAuthenticationState ).
  • Kontener stanu został zarejestrowany w kontenerze usług.
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

Dostosowywanie tras aplikacji

Biblioteka Microsoft.AspNetCore.Components.WebAssembly.Authentication używa tras przedstawionych w poniższej tabeli do reprezentowania różnych stanów uwierzytelniania.

Route Purpose
authentication/login Wyzwala operację logowania.
authentication/login-callback Zarządza wynikiem dowolnej operacji logowania.
authentication/login-failed Wyświetla komunikaty o błędach, gdy operacja logowania kończy się niepowodzeniem z jakiegoś powodu.
authentication/logout Wyzwala operację wylogowania.
authentication/logout-callback Obsługuje wynik operacji wylogowania.
authentication/logout-failed Wyświetla komunikaty o błędach, gdy operacja wylogowania kończy się niepowodzeniem z jakiegoś powodu.
authentication/logged-out Wskazuje, że użytkownik pomyślnie wylogował się.
authentication/profile Wyzwala operację edytowania profilu użytkownika.
authentication/register Wyzwala operację rejestrowania nowego użytkownika.

Trasy pokazane w poprzedniej tabeli można konfigurować za pomocą polecenia RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths. Podczas ustawiania opcji udostępniania tras niestandardowych upewnij się, że aplikacja ma trasę, która obsługuje każdą ścieżkę.

W poniższym przykładzie wszystkie ścieżki są poprzedzone prefiksem /security.

Authentication komponent (Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string? Action { get; set; }
}
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string Action { get; set; }
}

W pliku Program:

builder.Services.AddApiAuthorization(options => { 
    options.AuthenticationPaths.LogInPath = "security/login";
    options.AuthenticationPaths.LogInCallbackPath = "security/login-callback";
    options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
    options.AuthenticationPaths.LogOutPath = "security/logout";
    options.AuthenticationPaths.LogOutCallbackPath = "security/logout-callback";
    options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
    options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
    options.AuthenticationPaths.ProfilePath = "security/profile";
    options.AuthenticationPaths.RegisterPath = "security/register";
});

Jeśli wymaganie wymaga zupełnie innych ścieżek, ustaw ścieżki zgodnie z wcześniejszym opisem i renderuj element RemoteAuthenticatorView z wyraźnym parametrem akcji.

@page "/register"

<RemoteAuthenticatorView Action="RemoteAuthenticationActions.Register" />

Jeśli zdecydujesz się to zrobić, możesz podzielić interfejs użytkownika na różne strony.

Dostosowywanie interfejsu użytkownika uwierzytelniania

RemoteAuthenticatorView zawiera domyślny zestaw fragmentów interfejsu użytkownika dla każdego stanu uwierzytelniania. Każdy stan można dostosować, przekazując niestandardowy element RenderFragment. Aby dostosować wyświetlany tekst podczas początkowego procesu logowania, można zmienić RemoteAuthenticatorView w następujący sposób.

Authentication komponent (Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string? Action { get; set; }
}
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string Action { get; set; }
}

RemoteAuthenticatorView ma jeden fragment, którego można użyć dla każdej ścieżki uwierzytelniania pokazanej w poniższej tabeli.

Route Fragment
authentication/login <LoggingIn>
authentication/login-callback <CompletingLoggingIn>
authentication/login-failed <LogInFailed>
authentication/logout <LogOut>
authentication/logout-callback <CompletingLogOut>
authentication/logout-failed <LogOutFailed>
authentication/logged-out <LogOutSucceeded>
authentication/profile <UserProfile>
authentication/register <Registering>

Dostosowywanie użytkownika

Można dostosować użytkowników powiązanych z aplikacją.

Dostosowanie użytkownika za pomocą roszczeń dotyczących ładunku

W poniższym przykładzie uwierzytelnieni użytkownicy aplikacji otrzymują amr roszczenie dla każdą z metod uwierzytelniania użytkownika. Oświadczenie amr określa, w jaki sposób podmiot tokenu został uwierzytelniony w oświadczeniach ładunku platformy tożsamości firmy Microsoft w wersji 1.0. W tym przykładzie użyto niestandardowej klasy konta użytkownika na podstawie elementu RemoteUserAccount.

Utwórz klasę, która rozszerza klasę RemoteUserAccount . Poniższy przykład ustawia AuthenticationMethod właściwość na tablicę amr właściwości JSON użytkownika. AuthenticationMethod jest wypełniany automatycznie przez framework, gdy użytkownik jest uwierzytelniony.

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[]? AuthenticationMethod { get; set; }
}

Utwórz fabrykę, która rozszerza AccountClaimsPrincipalFactory<TAccount> możliwości tworzenia oświadczeń na podstawie metod uwierzytelniania użytkownika przechowywanych w programie CustomUserAccount.AuthenticationMethod:

using System.Security.Claims;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory(NavigationManager navigation,
    IAccessTokenProviderAccessor accessor)
    : AccountClaimsPrincipalFactory<CustomUserAccount>(accessor)
{
    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity != null && initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = (ClaimsIdentity)initialUser.Identity;

            if (account.AuthenticationMethod is not null)
            {
                foreach (var value in account.AuthenticationMethod)
                {
                    userIdentity.AddClaim(new Claim("amr", value));
                }
            }
        }

        return initialUser;
    }
}

Zarejestruj dostawcę uwierzytelniania CustomAccountFactory wykorzystywanego. Każda z następujących rejestracji jest prawidłowa:

  • AddOidcAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddOidcAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddMsalAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddApiAuthorization:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddApiAuthorization<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    

Grupy zabezpieczeń i role ME-ID z niestandardową klasą konta użytkownika

Aby uzyskać dodatkowy przykład dotyczący współpracy z grupami zabezpieczeń ME-ID, rolami administratora ME-ID oraz niestandardową klasą konta użytkownika, zobacz ASP.NET Core Blazor WebAssembly z grupami i rolami Microsoft Entra ID.

Prerenderowanie z uwierzytelnianiem

Wstępne przetwarzanie zawartości wymagającej uwierzytelniania i autoryzacji nie jest obecnie obsługiwane. Skorzystaj z poniższych instrukcji, aby po wykonaniu wskazówek z tematów dotyczących aplikacji zabezpieczeń, utworzyć aplikację, która:

  • Prerenderuje ścieżki, dla których autoryzacja nie jest wymagana.
  • Nie prerenderuj ścieżek, dla których wymagane są uprawnienia.

Client W przypadku pliku projektu Program należy uwzględnić rejestracje wspólnych usług w oddzielnej metodzie (na przykład utworzyć metodę ConfigureCommonServices w projekcie Client). Typowe usługi to te, które deweloper rejestruje do użytku zarówno w projektach klienta, jak i serwera.

public static void ConfigureCommonServices(IServiceCollection services)
{
    services.Add...;
}

W pliku Program:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...

builder.Services.AddScoped( ... );

ConfigureCommonServices(builder.Services);

await builder.Build().RunAsync();

Server W pliku projektu Program zarejestruj następujące dodatkowe usługi i wywołaj polecenie ConfigureCommonServices:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddRazorPages();
builder.Services.TryAddScoped<AuthenticationStateProvider, 
    ServerAuthenticationStateProvider>();

Client.Program.ConfigureCommonServices(services);

W metodzie Server projektu Startup.ConfigureServices zarejestruj następujące dodatkowe usługi i wywołaj metodę ConfigureCommonServices:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddRazorPages();
    services.AddScoped<AuthenticationStateProvider, 
        ServerAuthenticationStateProvider>();
    services.AddScoped<SignOutSessionStateManager>();

    Client.Program.ConfigureCommonServices(services);
}

Aby uzyskać więcej informacji na temat dostawcy uwierzytelniania serwera platformy Blazor (ServerAuthenticationStateProvider), zobacz ASP.NET Core authentication and authorization (Uwierzytelnianie i autoryzacja podstawowego Blazor serwera platformy).

Server W pliku projektu Pages/_Host.cshtml zastąp pomocnik tagów Component (<component ... />) następującym kodem:

<div id="app">
    @if (HttpContext.Request.Path.StartsWithSegments("/authentication"))
    {
        <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
            render-mode="WebAssembly" />
    }
    else
    {
        <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
            render-mode="WebAssemblyPrerendered" />
    }
</div>

W powyższym przykładzie:

  • Element zastępczy {CLIENT APP ASSEMBLY NAME} to nazwa zestawu aplikacji klienckiej (na przykład BlazorSample.Client).
  • Warunkowe sprawdzanie segmentu ścieżki /authentication :
    • Unika wstępnego renderowania (render-mode="WebAssembly") dla ścieżek uwierzytelniania.
    • Prerenders (render-mode="WebAssemblyPrerendered") dla ścieżek innych niż uwierzytelnianie.

Opcje hostowanych aplikacji i dostawców logowania innych firm

Podczas uwierzytelniania i autoryzowania hostowanej Blazor WebAssembly aplikacji u dostawcy innej firmy dostępnych jest kilka opcji uwierzytelniania użytkownika. Wybór zależy od wybranego scenariusza.

Aby uzyskać więcej informacji, zapoznaj się z Utrwalanie dodatkowych oświadczeń i tokenów od dostawców zewnętrznych na platformie ASP.NET Core.

Uwierzytelnianie użytkowników tylko w celu wywoływania chronionych interfejsów API innych firm

Uwierzytelnij użytkownika przy użyciu przepływu OAuth po stronie klienta przy zewnętrznym dostawcy API.

builder.services.AddOidcAuthentication(options => { ... });

W tym scenariuszu:

  • Serwer hostowania aplikacji nie odgrywa roli.
  • Interfejsy API na serwerze nie mogą być chronione.
  • Aplikacja może wywoływać tylko chronione interfejsy API innych firm.

Uwierzytelniaj użytkowników za pomocą zewnętrznego dostawcy i wywołuj chronione interfejsy API na serwerze hosta oraz u zewnętrznego dostawcy

Skonfiguruj Identity używając dostawcy logowania innej firmy. Uzyskaj tokeny wymagane do uzyskiwania dostępu do interfejsu API innej firmy i przechowuj je.

Gdy użytkownik loguje się, Identity zbiera tokeny dostępu i odświeżania w ramach procesu uwierzytelniania. W tym momencie dostępnych jest kilka metod dokonywania wywołań do zewnętrznych interfejsów API.

Pobieranie tokenu dostępu innej firmy przy użyciu tokenu dostępu serwera

Użyj tokenu dostępu wygenerowanego na serwerze, aby pobrać token dostępu innej firmy z punktu końcowego interfejsu API serwera. Z tego miejsca użyj zewnętrznego tokenu dostępu, aby wywołać zasoby API zewnętrznych firm bezpośrednio z poziomu klienta Identity.

Nie zalecamy tego podejścia. Takie podejście wymaga traktowania tokenu dostępu innej firmy tak, jakby został wygenerowany dla klienta publicznego. W protokole OAuth publiczna aplikacja nie posiada tajemnicy klienta, ponieważ nie można jej zaufać w kwestii bezpiecznego przechowywania tajemnic, a token dostępu generuje się dla poufnego klienta. Poufny klient to taki, który ma tajemnicę klienta i zakłada się, że potrafi bezpiecznie przechowywać tajemnice.

  • Token dostępu innej firmy może mieć dodatkowe zakresy umożliwiające wykonywanie poufnych operacji w oparciu o fakt, że token emitowany przez inną firmę dla bardziej zaufanego klienta.
  • Podobnie tokeny odświeżania nie powinny być wystawiane klientowi, który nie jest zaufany, ponieważ zapewnia klientowi nieograniczony dostęp, chyba że zostaną wprowadzone inne ograniczenia.

Wykonaj wywołania interfejsu API od klienta do interfejsu API serwera w celu wywołania interfejsów API innych firm

Wykonaj wywołanie interfejsu API od klienta do interfejsu API serwera. Z serwera pobierz token dostępu dla zasobu interfejsu API innej firmy i wydaj dowolne wywołanie.

Zalecamy takie podejście. Chociaż takie podejście wymaga dodatkowego przeskoku sieciowego przez serwer w celu wywołania interfejsu API innej firmy, ostatecznie powoduje to bezpieczniejsze środowisko:

  • Serwer może przechowywać tokeny odświeżania i upewnić się, że aplikacja nie utraci dostępu do zasobów innych firm.
  • Aplikacja nie może ujawniać tokenów dostępu z serwera, który może mieć bardziej poufne uprawnienia.

Używanie punktów końcowych openID Connect (OIDC) w wersji 2.0

Biblioteka uwierzytelniania i Blazor szablony projektów używają punktów końcowych OpenID Connect (OIDC) w wersji 1.0. Aby użyć punktu końcowego w wersji 2.0, skonfiguruj opcję elementu nośnego JwtBearerOptions.Authority JWT. W poniższym przykładzie identyfikator ME-ID został skonfigurowany dla wersji 2.0 poprzez dołączenie segmentu v2.0 do właściwości Authority:

using Microsoft.AspNetCore.Authentication.JwtBearer;

...

builder.Services.Configure<JwtBearerOptions>(
    JwtBearerDefaults.AuthenticationScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    });

Alternatywnie ustawienie można ustawić w pliku ustawień aplikacji (appsettings.json):

{
  "Local": {
    "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 w przypadku dostawców innych niż ME-ID, ustaw właściwość Authority bezpośrednio. Ustaw właściwość w JwtBearerOptions lub w pliku ustawień aplikacji (appsettings.json) za pomocą klucza Authority.

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 instrukcje dotyczące oświadczeń w tokenie tożsamości są dostępne w Dokumentacji referencyjnej oświadczeń tokenu tożsamości.

Konfigurowanie i używanie usługi gRPC w składnikach

Aby skonfigurować aplikację Blazor WebAssembly do korzystania z platformy gRPC platformy ASP.NET Core:

Note

Prerendering jest domyślnie włączony w s Blazor Web App, więc musisz najpierw uwzględnić renderowanie składnika z serwera, a następnie od klienta. Każdy wstępnie użyty stan powinien przepływać do klienta, aby można było go użyć ponownie. Aby uzyskać więcej informacji, zobacz ASP.NET Core prerendered state persistence (Trwałość stanu wstępnego) ASP.NET CoreBlazor.

Note

Wstępne renderowanie jest domyślnie włączone w aplikacjach hostowanych Blazor WebAssembly , dlatego najpierw należy uwzględnić renderowanie składnika z serwera, a następnie z klienta. Każdy wstępnie użyty stan powinien przepływać do klienta, aby można było go użyć ponownie. Aby uzyskać więcej informacji, zobacz integrowanie składników ASP.NET Core Razor z MVC lub Razor stronami w hostowanych Blazor WebAssembly rozwiązaniach.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;

...

builder.Services.AddScoped(sp =>
{
    var baseAddressMessageHandler = 
        sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
    baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
    var grpcWebHandler = 
        new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
    var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, 
        new GrpcChannelOptions { HttpHandler = grpcWebHandler });

    return new Greeter.GreeterClient(channel);
});

Składnik w aplikacji klienckiej może wykonywać wywołania gRPC przy użyciu klienta gRPC (Grpc.razor):

@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
    <input @bind="name" placeholder="Type your name" />
    <button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
    private string name = "Bert";
    private string? serverResponse;

    private async Task GetGreeting()
    {
        try
        {
            var request = new HelloRequest { Name = name };
            var reply = await GreeterClient.SayHelloAsync(request);
            serverResponse = reply.Message;
        }
        catch (Grpc.Core.RpcException ex)
            when (ex.Status.DebugException is 
                AccessTokenNotAvailableException tokenEx)
        {
            tokenEx.Redirect();
        }
    }
}

Aby użyć Status.DebugException właściwości, użyj Grpc.Net.Client wersji 2.30.0 lub nowszej.

Aby uzyskać więcej informacji, zobacz gRPC-Web w aplikacjach ASP.NET Core gRPC.

Zamień implementację AuthenticationService

W poniższych podsekcjach wyjaśniono, jak zastąpić:

  • Dowolna implementacja języka JavaScript AuthenticationService .
  • Biblioteka uwierzytelniania firmy Microsoft dla języka JavaScript (MSAL.js).

Zamień dowolną implementację języka JavaScript AuthenticationService

Utwórz bibliotekę języka JavaScript do obsługi niestandardowych szczegółów uwierzytelniania.

Warning

Wskazówki w tej sekcji to szczegóły implementacji domyślnej RemoteAuthenticationService<TRemoteAuthenticationState,TAccount,TProviderOptions>. Kod TypeScript w tej sekcji dotyczy konkretnie platformy ASP.NET Core na platformie .NET 7 i może ulec zmianie bez powiadomienia w nadchodzących wersjach platformy ASP.NET Core.

// .NET makes calls to an AuthenticationService object in the Window.
declare global {
  interface Window { AuthenticationService: AuthenticationService }
}

export interface AuthenticationService {
  // Init is called to initialize the AuthenticationService.
  public static init(settings: UserManagerSettings & AuthorizeServiceSettings, logger: any) : Promise<void>;

  // Gets the currently authenticated user.
  public static getUser() : Promise<{[key: string] : string }>;

  // Tries to get an access token silently.
  public static getAccessToken(options: AccessTokenRequestOptions) : Promise<AccessTokenResult>;

  // Tries to sign in the user or get an access token interactively.
  public static signIn(context: AuthenticationContext) : Promise<AuthenticationResult>;

  // Handles the sign-in process when a redirect is used.
  public static async completeSignIn(url: string) : Promise<AuthenticationResult>;

  // Signs the user out.
  public static signOut(context: AuthenticationContext) : Promise<AuthenticationResult>;

  // Handles the signout callback when a redirect is used.
  public static async completeSignOut(url: string) : Promise<AuthenticationResult>;
}

// The rest of these interfaces match their C# definitions.

export interface AccessTokenRequestOptions {
  scopes: string[];
  returnUrl: string;
}

export interface AccessTokenResult {
  status: AccessTokenResultStatus;
  token?: AccessToken;
}

export interface AccessToken {
  value: string;
  expires: Date;
  grantedScopes: string[];
}

export enum AccessTokenResultStatus {
  Success = 'Success',
  RequiresRedirect = 'RequiresRedirect'
}

export enum AuthenticationResultStatus {
  Redirect = 'Redirect',
  Success = 'Success',
  Failure = 'Failure',
  OperationCompleted = 'OperationCompleted'
};

export interface AuthenticationResult {
  status: AuthenticationResultStatus;
  state?: unknown;
  message?: string;
}

export interface AuthenticationContext {
  state?: unknown;
  interactiveRequest: InteractiveAuthenticationRequest;
}

export interface InteractiveAuthenticationRequest {
  scopes?: string[];
  additionalRequestParameters?: { [key: string]: any };
};

Bibliotekę można zaimportować, usuwając oryginalny <script> tag i dodając tag, który ładuje bibliotekę niestandardową <script> . W poniższym przykładzie pokazano zastąpienie tagu domyślnego <script> jednym, który ładuje bibliotekę o nazwie CustomAuthenticationService.js z wwwroot/js folderu.

Przed wwwroot/index.html skryptem (Blazor) wewnątrz tagu _framework/blazor.webassembly.js zamykającego</body>:

- <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>
+ <script src="js/CustomAuthenticationService.js"></script>

Aby uzyskać więcej informacji, zobacz AuthenticationService.ts w dotnet/aspnetcore repozytorium GitHub.

Note

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Zastąp bibliotekę uwierzytelniania firmy Microsoft dla języka JavaScript (MSAL.js)

Jeśli aplikacja wymaga niestandardowej wersji biblioteki Microsoft Authentication Library for JavaScript (MSAL.js), wykonaj następujące kroki:

  1. Upewnij się, że system ma najnowszy zestaw .NET SDK dla deweloperów lub uzyskaj i zainstaluj najnowszy zestaw SDK dla deweloperów z zestawu .NET SDK: Instalatory i pliki binarne. Konfiguracja wewnętrznych źródeł danych NuGet nie jest wymagana w tym scenariuszu.
  2. Skonfiguruj dotnet/aspnetcore repozytorium GitHub na potrzeby programowania zgodnie z dokumentacją w sekcji Build ASP.NET Core from Source (Kompilacja ASP.NET Core ze źródła). Sforkuj i sklonuj lub pobierz archiwum ZIP z repozytorium GitHub.
  3. src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json Otwórz plik i ustaw żądaną wersję programu @azure/msal-browser. Aby uzyskać listę wydanych wersji, odwiedź @azure/msal-browser witrynę internetową npm i wybierz kartę Wersje .
  4. Skompiluj projekt Authentication.Msal w folderze src/Components/WebAssembly/Authentication.Msal/src używając polecenia yarn build w konsoli.
  5. Jeśli aplikacja używa skompresowanych zasobów (Brotli/Gzip), skompresuj Interop/dist/Release/AuthenticationService.js plik.
  6. Skopiuj plik AuthenticationService.js i skompresowane wersje pliku (.br/.gz), jeśli zostały utworzone, z folderu Interop/dist/Release do folderu publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal w opublikowanych zasobach aplikacji.

Przekazywanie opcji dostawcy niestandardowego

Zdefiniuj klasę przekazywania danych do bazowej biblioteki Języka JavaScript.

Important

Struktura klasy musi być zgodna z oczekiwaniami biblioteki, gdy kod JSON jest serializowany za pomocą polecenia System.Text.Json.

W poniższym przykładzie przedstawiono klasę ProviderOptions z atrybutami JsonPropertyName odpowiadającymi hipotetycznym oczekiwaniom biblioteki dostawcy niestandardowego:

public class ProviderOptions
{
    public string? Authority { get; set; }
    public string? MetadataUrl { get; set; }
    
    [JsonPropertyName("client_id")]
    public string? ClientId { get; set; }
    
    public IList<string> DefaultScopes { get; set; } = [ "openid", "profile" ];
        
    [JsonPropertyName("redirect_uri")]
    public string? RedirectUri { get; set; }
    
    [JsonPropertyName("post_logout_redirect_uri")]
    public string? PostLogoutRedirectUri { get; set; }
    
    [JsonPropertyName("response_type")]
    public string? ResponseType { get; set; }
    
    [JsonPropertyName("response_mode")]
    public string? ResponseMode { get; set; }
}

Zarejestruj opcje dostawcy w systemie di i skonfiguruj odpowiednie wartości:

builder.Services.AddRemoteAuthentication<RemoteAuthenticationState, RemoteUserAccount,
    ProviderOptions>(options => {
        options.ProviderOptions.Authority = "...";
        options.ProviderOptions.MetadataUrl = "...";
        options.ProviderOptions.ClientId = "...";
        options.ProviderOptions.DefaultScopes = [ "openid", "profile", "myApi" ];
        options.ProviderOptions.RedirectUri = "https://localhost:5001/authentication/login-callback";
        options.ProviderOptions.PostLogoutRedirectUri = "https://localhost:5001/authentication/logout-callback";
        options.ProviderOptions.ResponseType = "...";
        options.ProviderOptions.ResponseMode = "...";
    });

W poprzednim przykładzie ustawiono identyfikatory URI przekierowania za pomocą zwykłych literałów ciągów. Dostępne są następujące alternatywy:

  • TryCreate używając IWebAssemblyHostEnvironment.BaseAddress:

    Uri.TryCreate(
        $"{builder.HostEnvironment.BaseAddress}authentication/login-callback", 
        UriKind.Absolute, out var redirectUri);
    options.RedirectUri = redirectUri;
    
  • Konfiguracja konstruktora hostów:

    options.RedirectUri = builder.Configuration["RedirectUri"];
    

    wwwroot/appsettings.json:

    {
      "RedirectUri": "https://localhost:5001/authentication/login-callback"
    }
    

Dodatkowe zasoby