ASP.NET Core Blazor Hybrid 驗證與授權

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱 本文的 ASP.NET Core 8.0 版本。

本文說明 ASP.NET Core 在 Blazor Hybrid 應用程式中設定及管理安全性和 ASP.NET Core Identity 的支援。

應用程式中的 Blazor Hybrid 驗證是由原生平台程式庫所處理,因為其提供瀏覽器沙箱所無法提供的增強安全性保證。 原生應用程式的驗證會使用 OS 特定機制或透過同盟通訊協定,例如 OpenID Connect (OIDC)。 請遵循您為應用程式選取的識別提供者指引,然後使用本文中的指引,進一步整合身分識別與 Blazor。

整合驗證必須達到下列 Razor 元件和服務的目標:

將驗證新增至 .NET MAUI、WPF 或 Windows Forms 應用程式,且使用者能夠成功登入及登出之後,請將驗證與 Blazor 整合,讓已驗證的使用者可供 Razor 元件和服務使用。 執行下列步驟:

.NET MAUI 應用程式使用 Xamarin.Essentials:Web AuthenticatorWebAuthenticator 類別可讓應用程式起始瀏覽器型驗證流程,以接聽向應用程式註冊的特定 URL 回呼。

如需其他指引,請參閱下列資源:

Windows Forms 應用程式使用 Microsoft 身分識別平台與 Microsoft Entra (ME-ID) 和 AAD B2C 整合。 如需詳細資訊,請參閱 Microsoft 驗證程式庫 (MSAL) 概觀

不使用使用者變更更新建立自訂 AuthenticationStateProvider

如果應用程式在應用程式啟動之後立即驗證使用者,且已驗證的使用者在整個應用程式存留期內維持不變,則不需要使用者變更通知,而且應用程式只會提供已驗證使用者的相關資訊。 在此案例中,使用者在開啟應用程式時登入應用程式,而應用程式會在使用者登出之後再次顯示登入畫面。下列 ExternalAuthStateProvider 是此驗證案例的自訂 AuthenticationStateProvider 範例實作。

注意

下列自訂 AuthenticationStateProvider 不會宣告命名空間,以便讓程式碼範例適用於任何 Blazor Hybrid 應用程式。 不過,最佳做法是當您在生產應用程式中實作範例時,提供應用程式的命名空間。

ExternalAuthStateProvider.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> authenticationState;

    public ExternalAuthStateProvider(AuthenticatedUser user) => 
        authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; } = new();
}

下列步驟描述如何:

  • 新增必要的命名空間。
  • 將授權服務和 Blazor 抽象概念新增至服務集合。
  • 建置服務集合。
  • 解析 AuthenticatedUser 服務來設定已驗證使用者的宣告主體。 請查看您識別提供者的文件以取得詳細資料。
  • 傳回建置的主機。

MauiProgram.csMauiProgram.CreateMauiApp 方法中,新增 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

移除下列程式碼,以傳回建置的 Microsoft.Maui.Hosting.MauiApp

- return builder.Build();

以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看您識別提供者的文件以取得詳細資料。

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

下列步驟描述如何:

  • 新增必要的命名空間。
  • 將授權服務和 Blazor 抽象概念新增至服務集合。
  • 建置服務集合,並將建置的服務集合新增為應用程式 ResourceDictionary 的資源。
  • 解析 AuthenticatedUser 服務來設定已驗證使用者的宣告主體。 請查看您識別提供者的文件以取得詳細資料。
  • 傳回建置的主機。

MainWindow 的建構函式 (MainWindow.xaml.cs) 中,新增 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

移除下列程式碼,其會將建置的服務集合新增為應用程式 ResourceDictionary 的資源:

- Resources.Add("services", serviceCollection.BuildServiceProvider());

以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看您識別提供者的文件以取得詳細資料。

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);

var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

下列步驟描述如何:

  • 新增必要的命名空間。
  • 將授權服務和 Blazor 抽象概念新增至服務集合。
  • 建置服務集合,並將建置的服務集合新增至應用程式的服務提供者。
  • 解析 AuthenticatedUser 服務來設定已驗證使用者的宣告主體。 請查看您識別提供者的文件以取得詳細資料。

Form1 的建構函式 (Form1.cs) 中,新增 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

移除下列程式程式碼,將建置的服務集合設定為應用程式的服務提供者:

- blazorWebView1.Services = services.BuildServiceProvider();

以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看您識別提供者的文件以取得詳細資料。

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;

var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

使用使用者變更更新建立自訂 AuthenticationStateProvider

若要在執行 Blazor 應用程式時更新使用者,請使用下列其中一種方法,在 AuthenticationStateProvider 實作內呼叫 NotifyAuthenticationStateChanged

BlazorWebView 外部發出驗證更新的訊號 (選項 1)

自訂 AuthenticationStateProvider 可以使用全域服務來發出驗證更新的訊號。 我們建議服務提供 AuthenticationStateProvider 可訂閱的事件,其中事件會叫用 NotifyAuthenticationStateChanged

注意

下列自訂 AuthenticationStateProvider 不會宣告命名空間,以便讓程式碼範例適用於任何 Blazor Hybrid 應用程式。 不過,最佳做法是當您在生產應用程式中實作範例時,提供應用程式的命名空間。

ExternalAuthStateProvider.cs

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState currentUser;

    public ExternalAuthStateProvider(ExternalAuthService service)
    {
        currentUser = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            currentUser = new AuthenticationState(newUser);
            NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(currentUser);
}

public class ExternalAuthService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

MauiProgram.csMauiProgram.CreateMauiApp 方法中,新增 Microsoft.AspNetCore.Components.Authorization 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;

將授權服務和 Blazor 抽象概念新增至服務集合:

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

MainWindow 的建構函式 (MainWindow.xaml.cs) 中,新增 Microsoft.AspNetCore.Components.Authorization 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;

將授權服務和 Blazor 抽象概念新增至服務集合:

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();

Form1 的建構函式 (Form1.cs) 中,新增 Microsoft.AspNetCore.Components.Authorization 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;

將授權服務和 Blazor 抽象概念新增至服務集合:

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();

無論應用程式在何處驗證使用者,請解析 ExternalAuthService 服務:

var authService = host.Services.GetRequiredService<ExternalAuthService>();

執行您的自訂 OpenID/MSAL 程式碼來驗證使用者。 請查看您識別提供者的文件以取得詳細資料。 已驗證使用者 (下列範例中的 authenticatedUser) 根據新的 ClaimsIdentity 是新的 ClaimsPrincipal

將目前使用者設定為已驗證的使用者:

authService.CurrentUser = authenticatedUser;

上述方法的替代方法是在 System.Threading.Thread.CurrentPrincipal 上設定使用者的主體,而不是透過服務進行設定,以避免使用相依性插入容器:

public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(
            new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ?? 
                new ClaimsPrincipal(new ClaimsIdentity())));
}

使用替代方法,只會將授權服務 (AddAuthorizationCore) 和 CurrentThreadUserAuthenticationStateProvider (.TryAddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()) 新增至服務集合。

處理 BlazorWebView 內的驗證 (選項 2)

自訂 AuthenticationStateProvider 可以包含其他方法來觸發登入和登出及更新使用者。

注意

下列自訂 AuthenticationStateProvider 不會宣告命名空間,以便讓程式碼範例適用於任何 Blazor Hybrid 應用程式。 不過,最佳做法是當您在生產應用程式中實作範例時,提供應用程式的命名空間。

ExternalAuthStateProvider.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithExternalProviderAsync();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
    {
        /*
            Provide OpenID/MSAL code to authenticate the user. See your identity 
            provider's documentation for details.

            Return a new ClaimsPrincipal based on a new ClaimsIdentity.
        */
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

        return Task.FromResult(authenticatedUser);
    }

    public void Logout()
    {
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}

在前述範例中:

  • 呼叫 LogInAsyncCore 會觸發登入程序。
  • 呼叫 NotifyAuthenticationStateChanged 會通知更新正在進行中,可讓應用程式在登入或登出程序期間提供暫時 UI。
  • 傳回 loginTask 會傳回工作,讓觸發登入的元件在工作完成之後可以等候和回應。
  • LoginWithExternalProviderAsync 方法是由開發人員實作,以身分識別提供者的 SDK 登入使用者。 如需詳細資訊,請參閱您識別提供者的文件。 已驗證的使用者 (authenticatedUser) 根據新的 ClaimsIdentity 是新的 ClaimsPrincipal

MauiProgram.csMauiProgram.CreateMauiApp 方法中,將授權服務和 Blazor 抽象概念新增至服務集合:

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

MainWindow.xaml.cs 的建構函式 (MainWindow) 中,將授權服務和 Blazor 抽象概念新增至服務集合:

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Form1.cs 的建構函式 (Form1) 中,將授權服務和 Blazor 抽象概念新增至服務集合:

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

下列 LoginComponent 元件示範如何登入使用者。 在一般應用程式中,只有在使用者未登入應用程式時,才會在父元件中顯示 LoginComponent 元件。

Shared/LoginComponent.razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
    public async Task Login()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .LogInAsync();
    }
}

下列 LogoutComponent 元件示範如何登出使用者。 在一般應用程式中,只有在使用者登入應用程式時,才會在父元件中顯示 LogoutComponent 元件。

Shared/LogoutComponent.razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
    public async Task Logout()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .Logout();
    }
}

存取其他驗證資訊

Blazor 不會定義處理其他認證的抽象概念,例如用於 Web API HTTP 要求的存取權杖。 建議您遵循下列識別提供者的指引,以識別提供者 SDK 所提供的基本類型來管理使用者的認證。

識別提供者 SDK 通常會針對儲存在裝置中的使用者認證使用權杖存放區。 如果將 SDK 的權杖存放區基本類型新增至服務容器,請取用應用程式內的 SDK 基本類型。

Blazor 架構不知道使用者的驗證認證,也不會以任何方式與認證互動,因此應用程式的程式碼可以自由地遵循您認為最方便的任何方法。 不過,當您在應用程式中實作驗證碼時,請遵循下一節 (其他驗證安全性考量) 中的一般安全性指引。

其他驗證安全性考量

驗證程序是在 Blazor 外部進行,因此建議開發人員存取識別提供者的指引,來取得其他安全性指引。

實作驗證時:

  • 避免在 Web View 的內容中進行驗證。 例如,請避免使用 JavaScript OAuth 程式庫來執行驗證流程。 在單頁應用程式中,驗證權杖不會隱藏在 JavaScript 中,且惡意使用者可以輕鬆地探索並用於惡意用途。 原生應用程式不會遭受此風險,因為原生應用程式只能在瀏覽器內容以外取得權杖,這表示惡意協力廠商指令碼無法竊取權杖並危害應用程式。
  • 請避免自行實作驗證工作流程。 在大部分情況下,平台程式庫會使用系統的瀏覽器 (而不是使用可遭攔截的自訂 Web View) 安全地處理驗證工作流程。
  • 避免使用平台的 Web View 控制項來執行驗證。 相反地,請盡可能依賴系統的瀏覽器。
  • 請避免將權杖傳遞至文件內容 (JavaScript)。 在某些情況下,需要文件內的 JavaScript 程式庫,才能執行外部服務的授權呼叫。 請勿透過 JS Interop 讓權杖可供 JavaScript 使用,而是:
    • 將產生的暫存權杖提供給程式庫,並在 Web View 內提供。
    • 攔截程式碼中的傳出網路要求。
    • 將暫存權杖取代為實際權杖,並確認要求的目的地是有效的。

其他資源

應用程式中的 Blazor Hybrid 驗證是由原生平台程式庫所處理,因為其提供瀏覽器沙箱所無法提供的增強安全性保證。 原生應用程式的驗證會使用 OS 特定機制或透過同盟通訊協定,例如 OpenID Connect (OIDC)。 請遵循您為應用程式選取的識別提供者指引,然後使用本文中的指引,進一步整合身分識別與 Blazor。

整合驗證必須達到下列 Razor 元件和服務的目標:

將驗證新增至 .NET MAUI、WPF 或 Windows Forms 應用程式,且使用者能夠成功登入及登出之後,請將驗證與 Blazor 整合,讓已驗證的使用者可供 Razor 元件和服務使用。 執行下列步驟:

.NET MAUI 應用程式使用 Xamarin.Essentials:Web AuthenticatorWebAuthenticator 類別可讓應用程式起始瀏覽器型驗證流程,以接聽向應用程式註冊的特定 URL 回呼。

如需其他指引,請參閱下列資源:

Windows Forms 應用程式使用 Microsoft 身分識別平台與 Microsoft Entra (ME-ID) 和 AAD B2C 整合。 如需詳細資訊,請參閱 Microsoft 驗證程式庫 (MSAL) 概觀

不使用使用者變更更新建立自訂 AuthenticationStateProvider

如果應用程式在應用程式啟動之後立即驗證使用者,且已驗證的使用者在整個應用程式存留期內維持不變,則不需要使用者變更通知,而且應用程式只會提供已驗證使用者的相關資訊。 在此案例中,使用者在開啟應用程式時登入應用程式,而應用程式會在使用者登出之後再次顯示登入畫面。下列 ExternalAuthStateProvider 是此驗證案例的自訂 AuthenticationStateProvider 範例實作。

注意

下列自訂 AuthenticationStateProvider 不會宣告命名空間,以便讓程式碼範例適用於任何 Blazor Hybrid 應用程式。 不過,最佳做法是當您在生產應用程式中實作範例時,提供應用程式的命名空間。

ExternalAuthStateProvider.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> authenticationState;

    public ExternalAuthStateProvider(AuthenticatedUser user) => 
        authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; } = new();
}

下列步驟描述如何:

  • 新增必要的命名空間。
  • 將授權服務和 Blazor 抽象概念新增至服務集合。
  • 建置服務集合。
  • 解析 AuthenticatedUser 服務來設定已驗證使用者的宣告主體。 請查看您識別提供者的文件以取得詳細資料。
  • 傳回建置的主機。

MauiProgram.csMauiProgram.CreateMauiApp 方法中,新增 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

移除下列程式碼,以傳回建置的 Microsoft.Maui.Hosting.MauiApp

- return builder.Build();

以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看您識別提供者的文件以取得詳細資料。

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

下列步驟描述如何:

  • 新增必要的命名空間。
  • 將授權服務和 Blazor 抽象概念新增至服務集合。
  • 建置服務集合,並將建置的服務集合新增為應用程式 ResourceDictionary 的資源。
  • 解析 AuthenticatedUser 服務來設定已驗證使用者的宣告主體。 請查看您識別提供者的文件以取得詳細資料。
  • 傳回建置的主機。

MainWindow 的建構函式 (MainWindow.xaml.cs) 中,新增 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

移除下列程式碼,其會將建置的服務集合新增為應用程式 ResourceDictionary 的資源:

- Resources.Add("services", serviceCollection.BuildServiceProvider());

以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看您識別提供者的文件以取得詳細資料。

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);

var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

下列步驟描述如何:

  • 新增必要的命名空間。
  • 將授權服務和 Blazor 抽象概念新增至服務集合。
  • 建置服務集合,並將建置的服務集合新增至應用程式的服務提供者。
  • 解析 AuthenticatedUser 服務來設定已驗證使用者的宣告主體。 請查看您識別提供者的文件以取得詳細資料。

Form1 的建構函式 (Form1.cs) 中,新增 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

移除下列程式程式碼,將建置的服務集合設定為應用程式的服務提供者:

- blazorWebView1.Services = services.BuildServiceProvider();

以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看您識別提供者的文件以取得詳細資料。

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;

var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

使用使用者變更更新建立自訂 AuthenticationStateProvider

若要在執行 Blazor 應用程式時更新使用者,請使用下列其中一種方法,在 AuthenticationStateProvider 實作內呼叫 NotifyAuthenticationStateChanged

BlazorWebView 外部發出驗證更新的訊號 (選項 1)

自訂 AuthenticationStateProvider 可以使用全域服務來發出驗證更新的訊號。 我們建議服務提供 AuthenticationStateProvider 可訂閱的事件,其中事件會叫用 NotifyAuthenticationStateChanged

注意

下列自訂 AuthenticationStateProvider 不會宣告命名空間,以便讓程式碼範例適用於任何 Blazor Hybrid 應用程式。 不過,最佳做法是當您在生產應用程式中實作範例時,提供應用程式的命名空間。

ExternalAuthStateProvider.cs

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState currentUser;

    public ExternalAuthStateProvider(ExternalAuthService service)
    {
        currentUser = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            currentUser = new AuthenticationState(newUser);
            NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(currentUser);
}

public class ExternalAuthService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

MauiProgram.csMauiProgram.CreateMauiApp 方法中,新增 Microsoft.AspNetCore.Components.Authorization 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;

將授權服務和 Blazor 抽象概念新增至服務集合:

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

MainWindow 的建構函式 (MainWindow.xaml.cs) 中,新增 Microsoft.AspNetCore.Components.Authorization 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;

將授權服務和 Blazor 抽象概念新增至服務集合:

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();

Form1 的建構函式 (Form1.cs) 中,新增 Microsoft.AspNetCore.Components.Authorization 的命名空間:

using Microsoft.AspNetCore.Components.Authorization;

將授權服務和 Blazor 抽象概念新增至服務集合:

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();

無論應用程式在何處驗證使用者,請解析 ExternalAuthService 服務:

var authService = host.Services.GetRequiredService<ExternalAuthService>();

執行您的自訂 OpenID/MSAL 程式碼來驗證使用者。 請查看您識別提供者的文件以取得詳細資料。 已驗證使用者 (下列範例中的 authenticatedUser) 根據新的 ClaimsIdentity 是新的 ClaimsPrincipal

將目前使用者設定為已驗證的使用者:

authService.CurrentUser = authenticatedUser;

上述方法的替代方法是在 System.Threading.Thread.CurrentPrincipal 上設定使用者的主體,而不是透過服務進行設定,以避免使用相依性插入容器:

public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(
            new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ?? 
                new ClaimsPrincipal(new ClaimsIdentity())));
}

使用替代方法,只會將授權服務 (AddAuthorizationCore) 和 CurrentThreadUserAuthenticationStateProvider (.AddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()) 新增至服務集合。

處理 BlazorWebView 內的驗證 (選項 2)

自訂 AuthenticationStateProvider 可以包含其他方法來觸發登入和登出及更新使用者。

注意

下列自訂 AuthenticationStateProvider 不會宣告命名空間,以便讓程式碼範例適用於任何 Blazor Hybrid 應用程式。 不過,最佳做法是當您在生產應用程式中實作範例時,提供應用程式的命名空間。

ExternalAuthStateProvider.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithExternalProviderAsync();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
    {
        /*
            Provide OpenID/MSAL code to authenticate the user. See your identity 
            provider's documentation for details.

            Return a new ClaimsPrincipal based on a new ClaimsIdentity.
        */
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

        return Task.FromResult(authenticatedUser);
    }

    public void Logout()
    {
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}

在前述範例中:

  • 呼叫 LogInAsyncCore 會觸發登入程序。
  • 呼叫 NotifyAuthenticationStateChanged 會通知更新正在進行中,可讓應用程式在登入或登出程序期間提供暫時 UI。
  • 傳回 loginTask 會傳回工作,讓觸發登入的元件在工作完成之後可以等候和回應。
  • LoginWithExternalProviderAsync 方法是由開發人員實作,以身分識別提供者的 SDK 登入使用者。 如需詳細資訊,請參閱您識別提供者的文件。 已驗證的使用者 (authenticatedUser) 根據新的 ClaimsIdentity 是新的 ClaimsPrincipal

MauiProgram.csMauiProgram.CreateMauiApp 方法中,將授權服務和 Blazor 抽象概念新增至服務集合:

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

MainWindow.xaml.cs 的建構函式 (MainWindow) 中,將授權服務和 Blazor 抽象概念新增至服務集合:

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Form1.cs 的建構函式 (Form1) 中,將授權服務和 Blazor 抽象概念新增至服務集合:

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

下列 LoginComponent 元件示範如何登入使用者。 在一般應用程式中,只有在使用者未登入應用程式時,才會在父元件中顯示 LoginComponent 元件。

Shared/LoginComponent.razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
    public async Task Login()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .LogInAsync();
    }
}

下列 LogoutComponent 元件示範如何登出使用者。 在一般應用程式中,只有在使用者登入應用程式時,才會在父元件中顯示 LogoutComponent 元件。

Shared/LogoutComponent.razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
    public async Task Logout()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .Logout();
    }
}

存取其他驗證資訊

Blazor 不會定義處理其他認證的抽象概念,例如用於 Web API HTTP 要求的存取權杖。 建議您遵循下列識別提供者的指引,以識別提供者 SDK 所提供的基本類型來管理使用者的認證。

識別提供者 SDK 通常會針對儲存在裝置中的使用者認證使用權杖存放區。 如果將 SDK 的權杖存放區基本類型新增至服務容器,請取用應用程式內的 SDK 基本類型。

Blazor 架構不知道使用者的驗證認證,也不會以任何方式與認證互動,因此應用程式的程式碼可以自由地遵循您認為最方便的任何方法。 不過,當您在應用程式中實作驗證碼時,請遵循下一節 (其他驗證安全性考量) 中的一般安全性指引。

其他驗證安全性考量

驗證程序是在 Blazor 外部進行,因此建議開發人員存取識別提供者的指引,來取得其他安全性指引。

實作驗證時:

  • 避免在 Web View 的內容中進行驗證。 例如,請避免使用 JavaScript OAuth 程式庫來執行驗證流程。 在單頁應用程式中,驗證權杖不會隱藏在 JavaScript 中,且惡意使用者可以輕鬆地探索並用於惡意用途。 原生應用程式不會遭受此風險,因為原生應用程式只能在瀏覽器內容以外取得權杖,這表示惡意協力廠商指令碼無法竊取權杖並危害應用程式。
  • 請避免自行實作驗證工作流程。 在大部分情況下,平台程式庫會使用系統的瀏覽器 (而不是使用可遭攔截的自訂 Web View) 安全地處理驗證工作流程。
  • 避免使用平台的 Web View 控制項來執行驗證。 相反地,請盡可能依賴系統的瀏覽器。
  • 請避免將權杖傳遞至文件內容 (JavaScript)。 在某些情況下,需要文件內的 JavaScript 程式庫,才能執行外部服務的授權呼叫。 請勿透過 JS Interop 讓權杖可供 JavaScript 使用,而是:
    • 將產生的暫存權杖提供給程式庫,並在 Web View 內提供。
    • 攔截程式碼中的傳出網路要求。
    • 將暫存權杖取代為實際權杖,並確認要求的目的地是有效的。

其他資源