ASP.NET Core Blazor Hybrid 驗證與授權
注意
這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本。
本文說明 ASP.NET Core 在 Blazor Hybrid 應用程式中設定及管理安全性和 ASP.NET Core Identity 的支援。
應用程式中的 Blazor Hybrid 驗證是由原生平台程式庫所處理,因為其提供瀏覽器沙箱所無法提供的增強安全性保證。 原生應用程式的驗證會使用 OS 特定機制或透過同盟通訊協定,例如 OpenID Connect (OIDC)。 請遵循您為應用程式選取的 identity 提供者指引,然後使用本文中的指引,進一步整合 identity 與 Blazor。
整合驗證必須達到下列 Razor 元件和服務的目標:
- 使用
Microsoft.AspNetCore.Components.Authorization
套件中的抽象概念,例如 AuthorizeView。 - 回應驗證內容中的變更。
- 從 identity 提供者存取應用程式所佈建的認證 (例如存取權杖),以執行授權的 API 呼叫。
將驗證新增至 .NET MAUI、WPF 或 Windows Forms 應用程式,且使用者能夠成功登入及登出之後,請將驗證與 Blazor 整合,讓已驗證的使用者可供 Razor 元件和服務使用。 執行下列步驟:
參考
Microsoft.AspNetCore.Components.Authorization
套件。注意
如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件) 的安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。
實作自訂 AuthenticationStateProvider,這個抽象概念可供 Razor 元件用來存取已驗證使用者的相關資訊,以及用來在驗證狀態變更時接收更新。
在相依性插入容器中註冊自訂驗證狀態提供者。
.NET MAUI 應用程式使用 Xamarin.Essentials:Web Authenticator:WebAuthenticator
類別可讓應用程式起始瀏覽器型驗證流程,以接聽向應用程式註冊的特定 URL 回呼。
如需其他指引,請參閱下列資源:
WPF 應用程式使用 Microsoft identity平台與 Microsoft Entra (ME-ID) 和 AAD B2C 整合。 如需指引和範例,請參閱下列資源:
Windows Forms 應用程式使用 Microsoft identity平台與 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
服務來設定已驗證使用者的宣告主體。 請查看identity提供者的文件以取得詳細資料。 - 傳回建置的主機。
在 MauiProgram.cs
的 MauiProgram.CreateMauiApp
方法中,新增 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 的命名空間:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
移除下列程式碼,以傳回建置的 Microsoft.Maui.Hosting.MauiApp:
- return builder.Build();
以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看identity提供者的文件以取得詳細資料。
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
服務來設定已驗證使用者的宣告主體。 請查看identity提供者的文件以取得詳細資料。 - 傳回建置的主機。
在 MainWindow
的建構函式 (MainWindow.xaml.cs
) 中,新增 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 的命名空間:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
移除下列程式碼,其會將建置的服務集合新增為應用程式 ResourceDictionary
的資源:
- Resources.Add("services", serviceCollection.BuildServiceProvider());
以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看identity提供者的文件以取得詳細資料。
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
服務來設定已驗證使用者的宣告主體。 請查看identity提供者的文件以取得詳細資料。
在 Form1
的建構函式 (Form1.cs
) 中,新增 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 的命名空間:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
移除下列程式程式碼,將建置的服務集合設定為應用程式的服務提供者:
- blazorWebView1.Services = services.BuildServiceProvider();
以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看identity提供者的文件以取得詳細資料。
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
外部發出驗證更新的訊號) - 處理
BlazorWebView
內的驗證
從 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.cs
的 MauiProgram.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 程式碼來驗證使用者。 請查看identity提供者的文件以取得詳細資料。 已驗證使用者 (下列範例中的 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
方法是由開發人員實作,以identity提供者的 SDK 登入使用者。 如需詳細資訊,請參閱identity提供者的文件。 已驗證的使用者 (authenticatedUser
) 根據新的 ClaimsIdentity 是新的 ClaimsPrincipal。
在 MauiProgram.cs
的 MauiProgram.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 要求的存取權杖。 建議您遵循下列identity提供者的指引,以identity提供者 SDK 所提供的基本類型來管理使用者的認證。
identity提供者 SDK 通常會針對儲存在裝置中的使用者認證使用權杖存放區。 如果將 SDK 的權杖存放區基本類型新增至服務容器,請取用應用程式內的 SDK 基本類型。
Blazor 架構不知道使用者的驗證認證,也不會以任何方式與認證互動,因此應用程式的程式碼可以自由地遵循您認為最方便的任何方法。 不過,當您在應用程式中實作驗證碼時,請遵循下一節 (其他驗證安全性考量) 中的一般安全性指引。
其他驗證安全性考量
驗證程序是在 Blazor 外部進行,因此建議開發人員存取identity提供者的指引,來取得其他安全性指引。
實作驗證時:
- 避免在 Web View 的內容中進行驗證。 例如,請避免使用 JavaScript OAuth 程式庫來執行驗證流程。 在單頁應用程式中,驗證權杖不會隱藏在 JavaScript 中,且惡意使用者可以輕鬆地探索並用於惡意用途。 原生應用程式不會遭受此風險,因為原生應用程式只能在瀏覽器內容以外取得權杖,這表示惡意協力廠商指令碼無法竊取權杖並危害應用程式。
- 請避免自行實作驗證工作流程。 在大部分情況下,平台程式庫會使用系統的瀏覽器 (而不是使用可遭攔截的自訂 Web View) 安全地處理驗證工作流程。
- 避免使用平台的 Web View 控制項來執行驗證。 相反地,請盡可能依賴系統的瀏覽器。
- 請避免將權杖傳遞至文件內容 (JavaScript)。 在某些情況下,需要文件內的 JavaScript 程式庫,才能執行外部服務的授權呼叫。 請勿透過 JS Interop 讓權杖可供 JavaScript 使用,而是:
- 將產生的暫存權杖提供給程式庫,並在 Web View 內提供。
- 攔截程式碼中的傳出網路要求。
- 將暫存權杖取代為實際權杖,並確認要求的目的地是有效的。
其他資源
應用程式中的 Blazor Hybrid 驗證是由原生平台程式庫所處理,因為其提供瀏覽器沙箱所無法提供的增強安全性保證。 原生應用程式的驗證會使用 OS 特定機制或透過同盟通訊協定,例如 OpenID Connect (OIDC)。 請遵循您為應用程式選取的 identity 提供者指引,然後使用本文中的指引,進一步整合 identity 與 Blazor。
整合驗證必須達到下列 Razor 元件和服務的目標:
- 使用
Microsoft.AspNetCore.Components.Authorization
套件中的抽象概念,例如 AuthorizeView。 - 回應驗證內容中的變更。
- 從 identity 提供者存取應用程式所佈建的認證 (例如存取權杖),以執行授權的 API 呼叫。
將驗證新增至 .NET MAUI、WPF 或 Windows Forms 應用程式,且使用者能夠成功登入及登出之後,請將驗證與 Blazor 整合,讓已驗證的使用者可供 Razor 元件和服務使用。 執行下列步驟:
參考
Microsoft.AspNetCore.Components.Authorization
套件。注意
如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件) 的安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。
實作自訂 AuthenticationStateProvider,這個抽象概念可供 Razor 元件用來存取已驗證使用者的相關資訊,以及用來在驗證狀態變更時接收更新。
在相依性插入容器中註冊自訂驗證狀態提供者。
.NET MAUI 應用程式使用 Xamarin.Essentials:Web Authenticator:WebAuthenticator
類別可讓應用程式起始瀏覽器型驗證流程,以接聽向應用程式註冊的特定 URL 回呼。
如需其他指引,請參閱下列資源:
WPF 應用程式使用 Microsoft identity平台與 Microsoft Entra (ME-ID) 和 AAD B2C 整合。 如需指引和範例,請參閱下列資源:
Windows Forms 應用程式使用 Microsoft identity平台與 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
服務來設定已驗證使用者的宣告主體。 請查看identity提供者的文件以取得詳細資料。 - 傳回建置的主機。
在 MauiProgram.cs
的 MauiProgram.CreateMauiApp
方法中,新增 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 的命名空間:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
移除下列程式碼,以傳回建置的 Microsoft.Maui.Hosting.MauiApp:
- return builder.Build();
以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看identity提供者的文件以取得詳細資料。
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
服務來設定已驗證使用者的宣告主體。 請查看identity提供者的文件以取得詳細資料。 - 傳回建置的主機。
在 MainWindow
的建構函式 (MainWindow.xaml.cs
) 中,新增 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 的命名空間:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
移除下列程式碼,其會將建置的服務集合新增為應用程式 ResourceDictionary
的資源:
- Resources.Add("services", serviceCollection.BuildServiceProvider());
以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看identity提供者的文件以取得詳細資料。
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
服務來設定已驗證使用者的宣告主體。 請查看identity提供者的文件以取得詳細資料。
在 Form1
的建構函式 (Form1.cs
) 中,新增 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 的命名空間:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
移除下列程式程式碼,將建置的服務集合設定為應用程式的服務提供者:
- blazorWebView1.Services = services.BuildServiceProvider();
以下列程式碼來取代上述程式碼。 新增 OpenID/MSAL 程式碼以驗證使用者。 請查看identity提供者的文件以取得詳細資料。
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
外部發出驗證更新的訊號) - 處理
BlazorWebView
內的驗證
從 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.cs
的 MauiProgram.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 程式碼來驗證使用者。 請查看identity提供者的文件以取得詳細資料。 已驗證使用者 (下列範例中的 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
方法是由開發人員實作,以identity提供者的 SDK 登入使用者。 如需詳細資訊,請參閱identity提供者的文件。 已驗證的使用者 (authenticatedUser
) 根據新的 ClaimsIdentity 是新的 ClaimsPrincipal。
在 MauiProgram.cs
的 MauiProgram.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 要求的存取權杖。 建議您遵循下列identity提供者的指引,以identity提供者 SDK 所提供的基本類型來管理使用者的認證。
identity提供者 SDK 通常會針對儲存在裝置中的使用者認證使用權杖存放區。 如果將 SDK 的權杖存放區基本類型新增至服務容器,請取用應用程式內的 SDK 基本類型。
Blazor 架構不知道使用者的驗證認證,也不會以任何方式與認證互動,因此應用程式的程式碼可以自由地遵循您認為最方便的任何方法。 不過,當您在應用程式中實作驗證碼時,請遵循下一節 (其他驗證安全性考量) 中的一般安全性指引。
其他驗證安全性考量
驗證程序是在 Blazor 外部進行,因此建議開發人員存取identity提供者的指引,來取得其他安全性指引。
實作驗證時:
- 避免在 Web View 的內容中進行驗證。 例如,請避免使用 JavaScript OAuth 程式庫來執行驗證流程。 在單頁應用程式中,驗證權杖不會隱藏在 JavaScript 中,且惡意使用者可以輕鬆地探索並用於惡意用途。 原生應用程式不會遭受此風險,因為原生應用程式只能在瀏覽器內容以外取得權杖,這表示惡意協力廠商指令碼無法竊取權杖並危害應用程式。
- 請避免自行實作驗證工作流程。 在大部分情況下,平台程式庫會使用系統的瀏覽器 (而不是使用可遭攔截的自訂 Web View) 安全地處理驗證工作流程。
- 避免使用平台的 Web View 控制項來執行驗證。 相反地,請盡可能依賴系統的瀏覽器。
- 請避免將權杖傳遞至文件內容 (JavaScript)。 在某些情況下,需要文件內的 JavaScript 程式庫,才能執行外部服務的授權呼叫。 請勿透過 JS Interop 讓權杖可供 JavaScript 使用,而是:
- 將產生的暫存權杖提供給程式庫,並在 Web View 內提供。
- 攔截程式碼中的傳出網路要求。
- 將暫存權杖取代為實際權杖,並確認要求的目的地是有效的。