ASP.NET Core Blazor WebAssembly dodatkowe scenariusze zabezpieczeń

Uwaga

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

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ą, zapoznaj się z wersją tego artykułu platformy .NET 8.

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, AccessTokenNotAvailableException zgłaszany jest token. AccessTokenNotAvailableException ma metodę Redirect , która przechodzi do AccessTokenResult.InteractiveRequestUrl metody przy użyciu podanej AccessTokenResult.InteractionOptions , aby umożliwić odświeżanie 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.

Uwaga

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 jej nie odwołuje.

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.

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 rozwiązania hostowanego Blazorna Blazor WebAssembly podstawie szablonu projektu identyfikatory URI żądań są domyślnie w podstawowym identyfikatorze URI aplikacji. IWebAssemblyHostEnvironment.BaseAddress W związku z tym (new Uri(builder.HostEnvironment.BaseAddress)) jest przypisywany do HttpClient.BaseAddress elementu 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 przy użyciu następujących metod co najmniej raz w nowym wystąpieniu InteractiveRequestOptionsprogramu :

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 wyodrębnieniu nazwy użytkownika z poprzedniego preferred_username logowania przy użyciu oświadczenia.

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 JSdane ON za pośrednictwem internetowego interfejsu API, dodatkowe parametry są dodawane do żądania przekierowania, jeśli token dostępu nie jest dostępny (AccessTokenNotAvailableException jest zgłaszany):

  • 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 wyodrębnieniu nazwy użytkownika z poprzedniego preferred_username logowania przy użyciu oświadczenia.
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 IAccessTokenProviderw przypadku korzystania z programu , zarządzaj 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 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 wyodrębnieniu nazwy użytkownika z poprzedniego preferred_username logowania przy użyciu oświadczenia.
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

Te wskazówki w tej sekcji są zalecane w przypadku aplikacji klienckich, które wysyłają żądania wychodzące do identyfikatorów URI, które nie należą do podstawowego identyfikatora URI aplikacji.

W poniższym przykładzie klasa niestandardowa rozszerza AuthorizationMessageHandler się do użycia jako DelegatingHandler dla klasy 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: new[] { "https://api.contoso.com/v1.0" },
            scopes: new[] { "example.read", "example.write" });
    }
}

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

Program W pliku CustomAuthorizationMessageHandler jest rejestrowany jako usługa przejściowa i jest skonfigurowany jako DelegatingHandler dla wystąpień wychodzących HttpResponseMessage wykonanych przez nazwę HttpClient.

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

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.

builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

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

Uwaga

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

W przypadku rozwiązania hostowanego naBlazor WebAssembly podstawie szablonuIWebAssemblyHostEnvironment.BaseAddress projektu (new Uri(builder.HostEnvironment.BaseAddress)) jest domyślnie przypisywany do elementu HttpClient.BaseAddress .Blazor

Skonfigurowany HttpClient jest używany do tworzenia autoryzowanych żądań przy użyciu try-catch wzorca. W przypadku utworzenia klienta za pomocą CreateClient programu (Microsoft.Extensions.Http pakietu) HttpClient są dostarczane wystąpienia zawierające tokeny dostępu podczas podejmowania żądań do interfejsu API serwera. Jeśli identyfikator URI żądania jest względnym identyfikatorem URI, tak jak w poniższym przykładzie (ExampleAPIMethod), jest połączony z elementem BaseAddress , gdy aplikacja kliencka wysyła żądanie:

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

Skonfigurować AuthorizationMessageHandler

AuthorizationMessageHandler Można skonfigurować przy użyciu autoryzowanych adresów URL, zakresów i zwracanego adresu URL przy użyciu ConfigureHandler metody . 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 program konfiguruje element HttpClient w Program pliku :

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

...

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

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

W przypadku rozwiązania hostowanego Blazor na Blazor WebAssembly podstawie szablonuIWebAssemblyHostEnvironment.BaseAddress projektu jest domyślnie przypisywany do następujących elementów:

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 {ASSEMBLY NAME}.Data;

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

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

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

        return forecasts ?? Array.Empty<WeatherForecast>();
    }
}
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {ASSEMBLY NAME}.Data;

public class WeatherForecastClient
{
    private readonly HttpClient http;
    private WeatherForecast[] forecasts;

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

    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 {ASSEMBLY NAME} to nazwa 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 jej nie odwołuje.

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 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 rozwiązania hostowanego naBlazor WebAssembly podstawie szablonuIWebAssemblyHostEnvironment.BaseAddress projektu (new Uri(builder.HostEnvironment.BaseAddress)) jest domyślnie przypisywany do elementu HttpClient.BaseAddress .Blazor

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ługi 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 jej nie odwołuje.

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

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 i example.write są ogólnymi przykładamiexample.read, które nie mają odzwierciedlać prawidłowych zakresów dla żadnego konkretnego dostawcy.

W przypadku rozwiązania hostowanego Blazor na Blazor WebAssembly podstawie szablonuIWebAssemblyHostEnvironment.BaseAddress projektu jest domyślnie przypisywany do następujących elementów:

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 jej nie odwołuje.

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

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

W przypadku rozwiązania hostowanego naBlazor WebAssembly podstawie szablonuIWebAssemblyHostEnvironment.BaseAddress projektu (new Uri(builder.HostEnvironment.BaseAddress)) jest domyślnie przypisywany do elementu HttpClient.BaseAddress .Blazor

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

        ...
    }
}

Uwaga

Kontroler w interfejsie API serwera w ExampleNoAuthenticationController poprzednim przykładzie nie jest oznaczony atrybutem[Authorize].

Decyzja, czy używać bezpiecznego klienta, czy niezabezpieczonego klienta, ponieważ wystąpienie domyślne HttpClient jest do dewelopera. 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 zabezpieczać punkty końcowe interfejsu API, użyj uwierzytelnionego HttpClient wystąpienia 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 dodatkowy zakres jest wymagany przez aplikację domyślnie 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}");
}

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

Uwaga

AdditionalScopesToConsent Program nie może aprowizować delegowanych uprawnień użytkownika dla programu Microsoft Graph za pośrednictwem interfejsu użytkownika zgody identyfikatora entra firmy Microsoft, gdy użytkownik najpierw używa 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 aprowizowania tokenu dostępu z danym zestawem zakresów.

W składniku Razor :

@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))
{
    ...
}

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

AccessTokenResult.TryGetToken Zwraca:

  • true za pomocą elementu token do użycia.
  • false jeśli token nie został pobrany.

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

Podczas wysyłania poświadczeń (nagłówków autoryzacji cookie) w żądaniach Authorization CORS nagłówek musi być dozwolony przez zasady MECHANIZMU CORS.

Następujące zasady obejmują konfigurację dla:

  • Źródła żądań (http://localhost:5000, https://localhost:5001).
  • Dowolna metoda (zlecenie).
  • 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 domyślnie 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 przykładowej aplikacji (Components/HTTPRequestTester.razor).

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

Gdy aplikacja jednostronicowa (SPA) uwierzytelnia użytkownika przy użyciu identyfikatora OpenID Połączenie (OIDC), stan uwierzytelniania jest utrzymywany lokalnie w SPA i w dostawcy (IP) w Identity 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 cookies 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 magazynie 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.

Zapisywanie stanu aplikacji przed operacją uwierzytelniania za pomocą magazynu 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 szablonuCounter projektu (Counter.razor). Metody serializacji i deserializacji kontenera są oparte na .System.Text.Json

using System.Text.Json;

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

    public string GetStateForLocalStorage()
    {
        return 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 obsługi 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; }
}
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

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

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

@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);
            }
        }
    }
}
@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 jest używana firma Microsoft Entra (ME-ID). W pliku Program:

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

builder.Services.AddSingleton<StateContainer>();

Dostosowywanie tras aplikacji

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

Marszruta Purpose
authentication/login Wyzwala operację logowania.
authentication/login-callback Obsługuje wynik 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ę wylogowywanie.
authentication/logout-callback Obsługuje wynik operacji wylogowywanie.
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 component (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 wywołuje zupełnie różne ścieżki, ustaw trasy zgodnie z opisem wcześniej i renderuj element RemoteAuthenticatorView z jawnym 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 RenderFragmentelement . Aby dostosować wyświetlany tekst podczas początkowego procesu logowania, można zmienić w RemoteAuthenticatorView następujący sposób.

Authentication component (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; }
}

Zawiera RemoteAuthenticatorView jeden fragment, którego można użyć na trasę uwierzytelniania pokazaną w poniższej tabeli.

Marszruta 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ą.

Dostosowywanie użytkownika przy użyciu oświadczenia ładunku

W poniższym przykładzie uwierzytelnieni użytkownicy aplikacji otrzymują amr oświadczenie dla każdej z metod uwierzytelniania użytkownika. Oświadczenie amr określa, w jaki sposób podmiot tokenu został uwierzytelniony w oświadczeniach ładunku platformy Microsoft Identity Platform 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 . W poniższym przykładzie AuthenticationMethod właściwość jest ustawiana na tablicę amrJSwłaściwości WŁ. AuthenticationMethod program jest wypełniany automatycznie przez platformę po uwierzytelnieniu użytkownika.

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

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[]? AuthenticationMethod { get; set; }
}
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 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigation, 
        IAccessTokenProviderAccessor accessor) : base(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;
    }
}
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigation, 
        IAccessTokenProviderAccessor accessor) : base(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;

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

        return initialUser;
    }
}

Zarejestruj dostawcę CustomAccountFactory uwierzytelniania w użyciu. 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 zapoznać się z dodatkowym przykładem, który współpracuje z grupami zabezpieczeń ME-ID i rolami Administracja istratora me-ID oraz niestandardową klasą konta użytkownika, zobacz ASP.NET Core Blazor WebAssembly z grupami i rolami microsoft Entra ID.

Wstępne używanie uwierzytelniania

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

  • Ścieżki prerenders, dla których autoryzacja nie jest wymagana.
  • Nie prerender ścieżek, dla których wymagana jest autoryzacja.

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 projekcieClient). 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:

  • Symbol 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 uwierzytelniania (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, zobacz 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 względem dostawcy interfejsu API innej firmy:

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.

Uwierzytelnianie użytkowników za pomocą dostawcy innej firmy i wywoływanie chronionych interfejsów API na serwerze hosta i innej firmy

Skonfiguruj Identity za pomocą 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 wykonywania wywołań interfejsu API do interfejsów API innych firm.

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 tokenu dostępu innej firmy, aby wywołać zasoby interfejsu API innych firm bezpośrednio z Identity poziomu klienta.

Nie zalecamy tego podejścia. Takie podejście wymaga traktowania tokenu dostępu innej firmy tak, jakby został wygenerowany dla klienta publicznego. W warunkach protokołu OAuth publiczna aplikacja nie ma wpisu tajnego klienta, ponieważ nie może być zaufana do bezpiecznego przechowywania wpisów tajnych, a token dostępu jest generowany dla poufnego klienta. Poufny klient to klient, który ma klucz tajny klienta i przyjmuje się, że można bezpiecznie przechowywać wpisy tajne.

  • 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 wyciekać tokenów dostępu z serwera, który może zawierać bardziej poufne uprawnienia.

Używanie punktów końcowych openID Połączenie (OIDC) w wersji 2.0

Biblioteka uwierzytelniania i Blazor szablony projektów używają punktów końcowych OpenID Połączenie (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 jest skonfigurowany dla wersji 2.0 przez dołączenie v2.0 segmentu Authority do właściwości:

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 tacking na segmencie do urzędu nie jest odpowiedni dla dostawcy OIDC aplikacji, na przykład z dostawcami innych niż ME-ID, ustaw Authority właściwość bezpośrednio. Ustaw właściwość w JwtBearerOptions pliku ustawień aplikacji () lub w pliku ustawień aplikacji (appsettings.json) przy użyciu Authority klucza.

Lista oświadczeń w zmianach tokenu identyfikatora dla punktów końcowych w wersji 2.0. Dokumentacja firmy Microsoft dotycząca zmian została wycofana, ale wskazówki dotyczące oświadczeń w tokenie identyfikatora są dostępne w dokumentacji oświadczeń tokenu identyfikatora.

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

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

Uwaga

Wstępne renderowanie jest domyślnie włączone w Blazor usłudze Web Apps, dlatego najpierw należy 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 Prerender ASP.NET Core components (Składniki prerender ASP.NET CoreRazor).

Uwaga

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 Prerender i integrowanie składników ASP.NET CoreRazor.

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 in ASP.NET Core gRPC apps (Aplikacje gRPC core).

Zastępowanie implementacji 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.

Ostrzeżenie

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 (_framework/blazor.webassembly.js) wewnątrz tagu Blazor 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.

Uwaga

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 Core 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). Rozwidlenie i sklonowanie lub pobranie archiwum dotnet/aspnetcore ZIP 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 Authentication.Msal projekt w folderze src/Components/WebAssembly/Authentication.Msal/src za yarn build pomocą polecenia w powłoce poleceń.
  5. Jeśli aplikacja używa skompresowanych zasobów (Brotli/Gzip), skompresuj Interop/dist/Release/AuthenticationService.js plik.
  6. AuthenticationService.js Skopiuj plik i skompresowane wersje () pliku ,.br.gz/ jeśli zostały utworzone, z Interop/dist/Release folderu do folderu aplikacji w opublikowanych zasobach aplikacji.publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal

Przekazywanie opcji dostawcy niestandardowego

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

Ważne

Struktura klasy musi być zgodna z oczekiwaniami biblioteki, gdy JSfunkcja WŁ. jest serializowana za pomocą System.Text.Jsonelementu .

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; } = 
        new List<string> { "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; }
}
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; } = 
        new List<string> { "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.Authority = "...";
        options.MetadataUrl = "...";
        options.ClientId = "...";
        options.DefaultScopes = new List<string> { "openid", "profile", "myApi" };
        options.RedirectUri = "https://localhost:5001/authentication/login-callback";
        options.PostLogoutRedirectUri = "https://localhost:5001/authentication/logout-callback";
        options.ResponseType = "...";
        options.ResponseMode = "...";
    });

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

  • TryCreate przy użyciu polecenia 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