使用 OpenID Connect (OIDC) 保護 ASP.NET Core Blazor Web App
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
本文說明如何使用 Blazor Web App 和 GitHub 存放庫 (dotnet/blazor-samples
) 中的應用程式範例,在 上進行保護 (.NET 8 或更新版本) (如何下載)。
本文的這個版本涵蓋了實作 OIDC,而不採用 前端後端 (BFF) 模式,並且採用全域互動式自動呈現的應用程式(包括伺服器和 .Client
專案)。 BFF 模式適用於對外部服務提出已驗證的要求。 如果應用程式的規格要求採用 BFF 模式,請將發行項版本選取器變更為 BFF 模式。
涵蓋下列規格:
- Blazor Web App 使用 自動轉譯模式搭配全域互動功能。
- 伺服器和用戶端應用程式使用自訂驗證狀態提供者服務擷取使用者的驗證狀態,並在伺服器與客戶端之間流動。
- 這個應用程式是任何 OIDC 驗證流程的起點。 OIDC 在應用程式手動設定,且不採用 Microsoft Entra ID 或 Microsoft Identity Web 套件,應用程式範例也不需要 Microsoft Azure 裝載。 不過,應用程式範例可與 Entra、Microsoft Identity Web 搭配使用,並且裝載於 Azure。
- 自動非互動式令牌刷新。
- 在伺服器專案安全呼叫 (web) API 以取得資料。
如需使用適用於 .NET的
範例應用程式
這個應用程式範例包含二個專案:
-
BlazorWebAppOidc
: Blazor Web App的伺服器端專案,包含天氣資料的 最小 API 端點範例。 -
BlazorWebAppOidc.Client
: Blazor Web App的用戶端專案。
使用下列連結,透過 Blazor 範例存放庫中的最新版本資料夾存取範例。 此範例位於 BlazorWebAppOidc
.NET 8 或更新版本的資料夾中。
檢視或下載範例程式碼 \(英文\) (如何下載)
伺服器端 Blazor Web App 專案 (BlazorWebAppOidc
)
BlazorWebAppOidc
專案是 Blazor Web App的伺服器端專案。
BlazorWebAppOidc.http
檔案可用於測試天氣資料要求。 請注意,BlazorWebAppOidc
專案必須執行才能測試端點,而且端點會硬式編碼到檔案中。 如需詳細資訊,請參閱在 Visual Studio 2022 中使用 .http 檔案。
組態
本節說明如何設定應用程式範例。
注意
針對 Microsoft Entra ID 或 Azure AD B2C,您可以從 Microsoft AddMicrosoftIdentityWebApp Web 使用Identity(Microsoft.Identity.Web
NuGet 套件、API 檔),這會將 OIDC 和Cookie驗證處理程式新增為適當的預設值。 本節中的應用程式範例和指導不使用 Microsoft Identity Web。 這份指導示範如何為任何 OIDC 提供者手動設定 OIDC 處理常式。 如需實作 Microsoft Identity Web 的詳細資訊,請參閱連結的資源。
建立客戶端密碼
警告
請勿在用戶端程式代碼中儲存應用程式秘密、連接字串、認證、密碼、個人標識碼 (PIN)、私人 C#/.NET 程式代碼或私鑰/令牌,這一律不安全。 在測試/預備和生產環境中,伺服器端 Blazor 程序代碼和 Web API 應該使用安全驗證流程,以避免在專案程式代碼或組態檔內維護認證。 在本機開發測試之外,建議您避免使用環境變數來儲存敏感數據,因為環境變數不是最安全的方法。 針對本機開發測試, 建議使用秘密管理員工具 來保護敏感數據。 如需詳細資訊,請參閱 安全地維護敏感數據和認證。
若要進行本機開發測試,請使用 秘密管理員工具 ,將伺服器應用程式的用戶端密碼儲存在組態密鑰 Authentication:Schemes:MicrosoftOidc:ClientSecret
之下。
注意
如果應用程式使用 Microsoft Entra ID 或 Azure AD B2C,請在 Entra 或 Azure 入口網站 的應用程式中註冊中建立用戶端密碼(管理>憑證和秘密>新用戶端密碼)。 在下列指引中使用新秘密的值。
範例應用程式尚未為秘密管理員工具初始化。 使用命令殼層,例如 Visual Studio 中的開發人員 PowerShell 命令殼層,執行下列命令。 在執行命令之前,請將 目錄與 cd
命令變更為伺服器項目的目錄。 命令會建立使用者密碼識別碼(<UserSecretsId>
在伺服器應用程式的項目檔中):
dotnet user-secrets init
執行下列命令來設定客戶端密碼。 占位符 {SECRET}
是從應用程式註冊中獲取的用戶端密鑰。
dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"
如果使用 Visual Studio,您可以用滑鼠右鍵按兩下 方案總管 中的伺服器專案,然後選取 [管理用戶密碼],以確認秘密已設定。
設定應用程式
在呼叫 OpenIdConnectOptions 時,在專案的 Program
檔案中找到下列 AddOpenIdConnect 設定:
PushedAuthorizationBehavior:控制 推送授權請求(PAR)的支援。 根據預設,如果識別提供者的探索文件(通常位於
.well-known/openid-configuration
)指明支援 PAR,則會使用 PAR。 如果您希望應用程式需要 PAR 支援,您可以指派一個值為PushedAuthorizationBehavior.Require
。 Microsoft Entra 不支援 PAR,而且未來沒有 Entra 支援它的計劃。oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.UseIfAvailable;
SignInScheme:設定與在成功驗證后負責保存使用者身分識別的中間件對應的驗證配置。 OIDC 處理常式必須使用能夠跨要求保存使用者認證的登入配置。 下方這一行字僅供示範之用。 如果省略,則會使用 DefaultSignInScheme 做為備用值。
oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
openid
和profile
(Scope) 的範圍 (選擇性):預設也會設定openid
和profile
範圍,因為 OIDC 處理常式需要它們才能運作,但如果Authentication:Schemes:MicrosoftOidc:Scope
設定包含範圍,則可能必須重新新增這些範圍。 如需一般設定指導,請參閱 ASP.NET Core 中的設定和 ASP.NET Core Blazor 設定。oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);
SaveTokens:定義了在成功授權之後,是否應該將存取和重新整理權杖儲存在 AuthenticationProperties 中。 此屬性會設定為
true
,以便將重新整理令牌儲存起來,用於非互動式的令牌重新整理過程。oidcOptions.SaveTokens = true;
離線存取的範圍 (Scope):重新整理權杖需要
offline_access
範圍。oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);
Authority 和 ClientId:設定 OIDC 呼叫的授權單位和用戶端識別碼。
oidcOptions.Authority = "{AUTHORITY}"; oidcOptions.ClientId = "{CLIENT ID}";
範例:
- 授權單位 (
{AUTHORITY}
):https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/
(使用租戶 IDaaaabbbb-0000-cccc-1111-dddd2222eeee
) - 用戶端識別碼 (
{CLIENT ID}
):00001111-aaaa-2222-bbbb-3333cccc4444
oidcOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/"; oidcOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
Microsoft Azure「通用」授權機構的範例:
「Common」權限應該用於多租戶應用程式。 您也可以針對單一租用戶應用程式使用「通用」授權方式,但必須使用自訂的 IssuerValidator,如本節稍後所示。
oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0/";
- 授權單位 (
ResponseType:將 OIDC 處理常式設定為只執行授權碼流程。 在這個模式中,不需要隱含授與和混合式流程。 OIDC 處理程序使用從授權端點傳回的授權碼,自動要求適當的權杖。
oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
注意
在 Entra 或 Azure 入口網站的隱含授與和混合式流程應用程式註冊設定中,請勿選取授權端點的核取方塊,以傳回 存取權杖 或 識別碼權杖。
MapInboundClaims 和 NameClaimType 與 RoleClaimType 的設定:許多 OIDC 伺服器在
name
使用 “role
” 和 “ClaimTypes”,而不是使用 SOAP/WS-Fed 預設值。 當 MapInboundClaims 設定為false
時,處理常式不會執行宣告對應,而且應用程式會直接使用 JWT 的宣告名稱。 下列範例會將角色宣告類型設定為「roles
」,這適用於 Microsoft Entra ID (ME-ID)。 如需詳細資訊,請參閱身分識別提供者的文件。注意
針對多數 OIDC 提供者,MapInboundClaims 必須設定為
false
,以防重新命名宣告。oidcOptions.MapInboundClaims = false; oidcOptions.TokenValidationParameters.NameClaimType = "name"; oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
路徑設定:路徑必須符合向 OIDC 提供者註冊應用程式時設定的重新導向 URI (登入回呼路徑),以及登出後重新導向 (登出回呼路徑)。 在 Azure 入口網站,路徑是在應用程式註冊的驗證面板設定。 登入和登出路徑都必須註冊為重新導向 URI。 預設值是
/signin-oidc
和/signout-callback-oidc
。CallbackPath:應用程式基本路徑中傳回 user-agent 的要求路徑。
在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost:{PORT}/signin-oidc
注意
使用 Microsoft Entra ID 時,
localhost
位址不需要連接埠。 大部分的其他 OIDC 提供者都需要正確的埠。SignedOutCallbackPath (組態鑰匙:"
SignedOutCallbackPath
"):OIDC 處理程式攔截的要求路徑位於應用程式的基底路徑中,當使用者代理從身份提供者註銷後,會首先被返回至此處理程式。 範例應用程式不會設定路徑的值,因為會使用預設值 「/signout-callback-oidc
」。。 攔截要求之後,如果指定,OIDC 處理程式會重新導向至 SignedOutRedirectUri 或 RedirectUri。在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost:{PORT}/signout-callback-oidc
注意
使用 Microsoft Entra 識別符時,請在 Entra 或 Azure 入口網站中的 Web 平臺組態 重新導向 URI 項目中設定路徑。 使用 Entra 時,
localhost
位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。 如果您未將已登出的回呼路徑 URI 新增至 Entra 中應用程式的註冊,Entra 將拒絕將使用者重新導回應用程式,只會要求他們關閉瀏覽器視窗。RemoteSignOutPath:在這個路徑收到的要求會讓處理常式使用登出方案來叫用登出。
在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost/signout-oidc
注意
使用 Microsoft Entra ID 時,請在 Entra 或 Azure 入口網站中設定 前端通道登出 URL。 使用 Entra 時,
localhost
位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。oidcOptions.CallbackPath = new PathString("{PATH}"); oidcOptions.SignedOutCallbackPath = new PathString("{PATH}"); oidcOptions.RemoteSignOutPath = new PathString("{PATH}");
範例 (預設值):
oidcOptions.CallbackPath = new PathString("/signin-oidc"); oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc"); oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");
(Microsoft Azure 僅使用「通用」端點) TokenValidationParameters.IssuerValidator:許多 OIDC 提供者會使用預設憑證簽發者驗證程式,但我們必須考慮以
{TENANT ID}
傳回之租用戶標識碼 (https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
) 參數化的憑證簽發者。 如需詳細資訊,請參閱 關於 OpenID Connect 和 Azure AD「通用」端點的 SecurityTokenInvalidIssuerException (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet
#1731)。僅適用於使用 Microsoft Entra ID 或 Azure AD B2C 搭配「通用」端點的應用程式:
var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority); oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;
應用程式範例的程式碼
檢查應用程式範例是否有下列功能:
- 透過自訂 cookie 刷新器 (
CookieOidcRefresher.cs
) 協助的自動無需互動令牌刷新。 - 伺服器專案會呼叫 AddAuthenticationStateSerialization 來新增伺服器端驗證狀態提供者,使用 PersistentComponentState 將驗證狀態傳送至用戶端。 用戶端會呼叫 AddAuthenticationStateDeserialization 來反序列化並使用伺服器所傳遞的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。
-
Blazor Web App 檔案 (
/weather-forecast
) 中的最小 API 端點 (Program
) 會處理Program.cs
的天氣資料範例要求。 端點需要藉由呼叫 RequireAuthorization 來進行授權。 針對您新增至專案的任何控制器,請將[Authorize]
屬性 新增至控制器或動作。 如需要求應用程式內跨系統授權的 授權原則 的詳細資訊,以及如何在部分公用端點選擇退出授權,請參閱 Razor Pages OIDC 指引。 - 這個應用程式在伺服器專案中安全地呼叫 (web) API 以獲取天氣數據。
- 在伺服器轉譯
Weather
元件時,元件會使用伺服器上的ServerWeatherForecaster
直接取得天氣資料 (而不是透過 Web API 呼叫)。 - 在用戶端轉譯元件時,元件會使用
ClientWeatherForecaster
服務實作,它會使用預先設定的 HttpClient (位於用戶端專案的Program
檔案) 對伺服器專案進行 Web API 呼叫。 伺服器專案/weather-forecast
檔案定義的最小 API 端點 (Program
),會從ServerWeatherForecaster
取得天氣資料,並將資料傳回用戶端。
- 在伺服器轉譯
- 透過自訂 cookie 刷新器 (
CookieOidcRefresher.cs
) 協助的自動無需互動令牌刷新。 -
PersistingAuthenticationStateProvider
類別 (PersistingAuthenticationStateProvider.cs
) 是伺服器端 AuthenticationStateProvider,使用 PersistentComponentState 將驗證狀態傳遞到用戶端,接著在 WebAssembly 應用程式的存留期內保持固定。 -
Blazor Web App 檔案 (
/weather-forecast
) 中的最小 API 端點 (Program
) 會處理Program.cs
的天氣資料範例要求。 端點需要藉由呼叫 RequireAuthorization 來進行授權。 針對您新增至專案的任何控制器,請將[Authorize]
屬性 新增至控制器或動作。 - 這個應用程式在伺服器專案中安全地呼叫 (web) API 以獲取天氣數據。
- 在伺服器轉譯
Weather
元件時,元件會使用伺服器上的ServerWeatherForecaster
直接取得天氣資料 (而不是透過 Web API 呼叫)。 - 在用戶端轉譯元件時,元件會使用
ClientWeatherForecaster
服務實作,它會使用預先設定的 HttpClient (位於用戶端專案的Program
檔案) 對伺服器專案進行 Web API 呼叫。 伺服器專案/weather-forecast
檔案定義的最小 API 端點 (Program
),會從ServerWeatherForecaster
取得天氣資料,並將資料傳回用戶端。
- 在伺服器轉譯
如需在 Blazor Web App 中使用服務抽象概念呼叫 Web API 的詳細資訊,請參閱 從 ASP.NET Core Blazor 應用程式呼叫 Web API。
Blazor Web App 用戶端專案 (BlazorWebAppOidc.Client
)
BlazorWebAppOidc.Client
專案是 Blazor Web App的用戶端專案。
用戶端會呼叫 AddAuthenticationStateDeserialization 來反序列化並使用伺服器所傳遞的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。
PersistentAuthenticationStateProvider
類別 (PersistentAuthenticationStateProvider.cs
) 是一個客戶端 AuthenticationStateProvider,藉由尋找在伺服器端渲染時保存在頁面中的資料,來判斷使用者的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。
如果使用者需要登入或登出,則需要完整頁面重新載入。
應用程式範例提供使用者名稱和電子郵件僅供顯示之用。 在提出後續要求時不包含用於向伺服器驗證的權杖,而是採用一種個別運作的方式,該方式將 cookie 包含在向伺服器提出的 HttpClient 要求中。
本文的這個版本涵蓋實作 OIDC,並且未採用 前端後端(BFF)模式 的應用程式,而是採用全域互動式伺服器渲染(單一專案)。 BFF 模式適用於對外部服務提出已驗證的要求。 如果應用程式的規格要求採用具有全域互動式自動轉譯的 BFF 模式 ,請將發行項版本選取器變更為 BFF 模式。
涵蓋下列規格:
- Blazor Web App 使用 伺服器渲染模式與全域互動。
- 這個應用程式是任何 OIDC 驗證流程的起點。 OIDC 在應用程式手動設定,且不採用 Microsoft Entra ID 或 Microsoft Identity Web 套件,應用程式範例也不需要 Microsoft Azure 裝載。 不過,應用程式範例可與 Entra、Microsoft Identity Web 搭配使用,並且裝載於 Azure。
- 自動非互動式令牌刷新。
如需使用適用於 .NET的
範例應用程式
範例應用程式是由單一伺服器端 Blazor Web App 專案 (BlazorWebAppOidcServer
) 所組成。
使用下列連結,透過 Blazor 範例存放庫中的最新版本資料夾存取範例。 此範例位於 BlazorWebAppOidcServer
.NET 8 或更新版本的資料夾中。
檢視或下載範例程式碼 \(英文\) (如何下載)
組態
本節說明如何設定應用程式範例。
注意
針對 Microsoft Entra ID 或 Azure AD B2C,您可以使用 AddMicrosoftIdentityWebApp(Microsoft.Identity.Web
、API 文件),這會新增 OIDC 和 Cookie,並提供適當的預設值。 本節中的應用程式範例和指導不使用 Microsoft Identity Web。 這份指導示範如何為任何 OIDC 提供者手動設定 OIDC 處理常式。 如需實作 Microsoft Identity Web 的詳細資訊,請參閱連結的資源。
建立客戶端密碼
警告
請勿在用戶端程式代碼中儲存應用程式秘密、連接字串、認證、密碼、個人標識碼 (PIN)、私人 C#/.NET 程式代碼或私鑰/令牌,這一律不安全。 在測試/預備和生產環境中,伺服器端 Blazor 程序代碼和 Web API 應該使用安全驗證流程,以避免在專案程式代碼或組態檔內維護認證。 在本機開發測試之外,建議您避免使用環境變數來儲存敏感數據,因為環境變數不是最安全的方法。 針對本機開發測試, 建議使用秘密管理員工具 來保護敏感數據。 如需詳細資訊,請參閱 安全地維護敏感數據和認證。
若要進行本機開發測試,請使用 Secret Manager 工具,將應用程式的用戶端密碼儲存在組態密鑰底下,Authentication:Schemes:MicrosoftOidc:ClientSecret
。
注意
如果應用程式使用 Microsoft Entra ID 或 Azure AD B2C,請在 Entra 或 Azure 入口網站 的應用程式中註冊中建立用戶端密碼(管理>憑證和秘密>新用戶端密碼)。 在以下指引中使用新的秘密值。
範例應用程式尚未為秘密管理員工具初始化。 使用命令殼層,例如 Visual Studio 中的開發人員 PowerShell 命令殼層,執行下列命令。 在執行命令之前,請使用 cd
命令將目錄變更為項目的目錄。 命令會建立使用者密碼識別碼(<UserSecretsId>
在應用程式的項目檔中):
dotnet user-secrets init
執行下列命令來設定客戶端密碼。 占位符 {SECRET}
是從應用程式註冊中獲取的用戶端密鑰。
dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"
如果使用 Visual Studio,您可以在 [方案總管] 中以滑鼠右鍵按一下專案,然後選取 [管理使用者密碼]來確認秘密是否已設定。
設定應用程式
在呼叫 OpenIdConnectOptions 時,在專案的 Program
檔案中找到下列 AddOpenIdConnect 設定:
PushedAuthorizationBehavior:控制 推送授權請求(PAR)支援。 預設情況下,如果身份提供者的探索文件(通常位於
.well-known/openid-configuration
)宣告支持 PAR,此設定將使用 PAR。 如果您想要要求應用程式的 PAR 支援,您可以指派PushedAuthorizationBehavior.Require
的值。 Microsoft Entra 不支援 PAR,而且未來沒有 Entra 支援它的計劃。oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.UseIfAvailable;
SignInScheme:設定與在成功驗證后負責保存使用者身分識別的中間件對應的驗證配置。 OIDC 處理常式必須使用能夠跨要求保存使用者認證的登入配置。 下方這一行字僅供示範之用。 如果省略,則會使用 DefaultSignInScheme 做為備用值。
oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
openid
和profile
(Scope) 的範圍 (選擇性):預設也會設定openid
和profile
範圍,因為 OIDC 處理常式需要它們才能運作,但如果Authentication:Schemes:MicrosoftOidc:Scope
設定包含範圍,則可能必須重新新增這些範圍。 如需一般設定指導,請參閱 ASP.NET Core 中的設定和 ASP.NET Core Blazor 設定。oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);
SaveTokens:定義了在成功授權之後,是否應該將存取和重新整理權杖儲存在 AuthenticationProperties 中。 此屬性會設定為
true
,以便將重新整理令牌儲存起來,用於非互動式的令牌重新整理過程。oidcOptions.SaveTokens = true;
離線存取的範圍 (Scope):重新整理權杖必須有
offline_access
範圍。oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);
Authority 和 ClientId:設定 OIDC 呼叫的授權單位和用戶端識別碼。
oidcOptions.Authority = "{AUTHORITY}"; oidcOptions.ClientId = "{CLIENT ID}";
範例:
- 授權單位 (
{AUTHORITY}
):https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/
(使用租戶 IDaaaabbbb-0000-cccc-1111-dddd2222eeee
) - 用戶端識別碼 (
{CLIENT ID}
):00001111-aaaa-2222-bbbb-3333cccc4444
oidcOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/"; oidcOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
Microsoft Azure「通用」授權單位的範例:
「Common」權限應該用於多租戶應用程式。 您也可以針對單一租用戶應用程式使用「通用」授權方式,但必須使用自訂的 IssuerValidator,如本節稍後所示。
oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0/";
- 授權單位 (
ResponseType:將 OIDC 處理常式設定為只執行授權碼流程。 在此模式下,隱式授權和混合式流程都是不必要的。 OIDC 處理程序使用從授權端點傳回的授權碼,自動要求適當的權杖。
oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
注意
在 Entra 或 Azure 入口網站的隱式授權與混合流程應用程式註冊設定中,不要勾選授權端點返回 存取權杖或 ID 權杖的任一選項。
MapInboundClaims 和 NameClaimType 與 RoleClaimType 的設定:許多 OIDC 伺服器在 ClaimTypes 中使用 "
name
" 和 "role
" 而不是 SOAP/WS-Fed 預設值。 當 MapInboundClaims 設定為false
時,處理常式不會執行宣告對應,而且應用程式會直接使用 JWT 的宣告名稱。 下列範例會將角色宣告類型設定為「roles
」,這適用於 Microsoft Entra ID (ME-ID)。 如需詳細資訊,請參閱身分識別提供者的文件。注意
針對多數 OIDC 提供者,MapInboundClaims 必須設定為
false
,以防重新命名宣告。oidcOptions.MapInboundClaims = false; oidcOptions.TokenValidationParameters.NameClaimType = "name"; oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
路徑設定:路徑必須符合向 OIDC 提供者註冊應用程式時設定的重新導向 URI (登入回呼路徑),以及登出後重新導向 (登出回呼路徑)。 在 Azure 入口網站,路徑是在應用程式註冊的驗證面板設定。 登入和登出路徑都必須註冊為重新導向 URI。 預設值是
/signin-oidc
和/signout-callback-oidc
。CallbackPath:應用程式基本路徑中傳回 user-agent 的要求路徑。
在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost:{PORT}/signin-oidc
注意
使用 Microsoft Entra ID 時,
localhost
位址不需要連接埠。 大部分的其他 OIDC 提供者都需要正確的埠。SignedOutCallbackPath (組態鑰匙:"
SignedOutCallbackPath
"):OIDC 處理程式攔截的要求路徑位於應用程式的基底路徑中,當使用者代理從身份提供者註銷後,會首先被返回至此處理程式。 範例應用程式不會設定路徑的值,因為會使用預設值 「/signout-callback-oidc
」。。 攔截要求之後,如果指定,OIDC 處理程式會重新導向至 SignedOutRedirectUri 或 RedirectUri。在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost:{PORT}/signout-callback-oidc
注意
使用 Microsoft Entra 識別符時,請在 Entra 或 Azure 入口網站中的 Web 平臺組態 重新導向 URI 項目中設定路徑。 使用 Entra 時,
localhost
位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。 如果您未將已登出的回呼路徑 URI 新增至 Entra 中應用程式的註冊,Entra 將拒絕將使用者重新導回應用程式,只會要求他們關閉瀏覽器視窗。RemoteSignOutPath:在這個路徑收到的要求會讓處理常式使用登出方案來叫用登出。
在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost/signout-oidc
注意
使用 Microsoft Entra ID 時,請在 Entra 或 Azure 入口網站中設定 前端通道登出 URL。 使用 Entra 時,
localhost
位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。oidcOptions.CallbackPath = new PathString("{PATH}"); oidcOptions.SignedOutCallbackPath = new PathString("{PATH}"); oidcOptions.RemoteSignOutPath = new PathString("{PATH}");
範例 (預設值):
oidcOptions.CallbackPath = new PathString("/signin-oidc"); oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc"); oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");
(Microsoft Azure 僅使用「通用」端點) TokenValidationParameters.IssuerValidator:許多 OIDC 提供者會使用預設憑證簽發者驗證程式,但我們必須考慮以
{TENANT ID}
傳回之租用戶標識碼 (https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
) 參數化的憑證簽發者。 如需詳細資訊,請參閱 關於 OpenID Connect 和 Azure AD「通用」端點的 SecurityTokenInvalidIssuerException (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet
#1731)。僅適用於使用 Microsoft Entra ID 或 Azure AD B2C 搭配「通用」端點的應用程式:
var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority); oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;
應用程式範例的程式碼
檢查應用程式範例是否有下列功能:
- 透過自訂 cookie 刷新器 (
CookieOidcRefresher.cs
) 協助的自動無需互動令牌刷新。 -
Weather
元件會使用[Authorize]
屬性 來防止未經授權的存取。 如需要求應用程式內跨系統授權的 授權原則 的詳細資訊,以及如何在部分公用端點選擇退出授權,請參閱 Razor Pages OIDC 指引。 如需了解此應用程式如何保障天氣數據的更多資訊,請參閱 互動式自動轉譯中的 Blazor Web App安全數據。
這個版本的文章介紹採用後端為前端 (BFF) 模式實作 OIDC。 如果應用程式規格不要求採用 BFF 模式,請將發行項版本選取器變更為 非 BFF 模式(互動式自動轉譯) 或 非 BFF 模式(互動式伺服器) (互動式伺服器轉譯)。
涵蓋下列規格:
- Blazor Web App 使用 自動轉譯模式搭配全域互動功能。
- 伺服器和用戶端應用程式使用自訂驗證狀態提供者服務擷取使用者的驗證狀態,並在伺服器與客戶端之間流動。
- 這個應用程式是任何 OIDC 驗證流程的起點。 OIDC 在應用程式手動設定,且不採用 Microsoft Entra ID 或 Microsoft Identity Web 套件,應用程式範例也不需要 Microsoft Azure 裝載。 不過,應用程式範例可與 Entra、Microsoft Identity Web 搭配使用,並且裝載於 Azure。
- 自動非互動式令牌刷新。
- 採用前端的後端 (BFF) 模式,使用 .NET Aspire 進行服務探索,並使用 YARP 將請求代理到後端應用程式的天氣預報端點。
- 後端 Web API 使用 JWT 持有者身份驗證,以驗證登入 Blazor Web App 中 cookie 所儲存的 JWT 權杖。
- Aspire 可改善建置 .NET 雲端原生應用程式的體驗。 它提供一組一致固定的工具和模式,用於建置和執行分散式應用程式。
- YARP (又一個反向 Proxy) 是用來建立反向 Proxy 伺服器的程式庫。
如需了解 .NET Aspire 的更多資訊,請參閱 《.NET Aspire 正式發行:簡化 .NET 雲端原生開發(2024 年 5 月)》。
先決條件
.NET Aspire 需要 Visual Studio 17.10 版或更新版本。
範例應用程式
這個應用程式範例包含五個專案:
-
.NET Aspire:
-
Aspire.AppHost
:用來管理應用程式的高階協作事務。 -
Aspire.ServiceDefaults
:包含預設的 .NET Aspire 應用程式設定,該設定可視需要加以擴充及自訂。
-
-
MinimalApiJwt
:後端 Web API,包含天氣資料的最小 API 端點範例。 -
BlazorWebAppOidc
: Blazor Web App的伺服器端專案。 -
BlazorWebAppOidc.Client
: Blazor Web App的用戶端專案。
使用下列連結,透過 Blazor 範例存放庫中的最新版本資料夾存取範例。 此範例位於 BlazorWebAppOidcBff
.NET 8 或更新版本的資料夾中。
檢視或下載範例程式碼 \(英文\) (如何下載)
.NET Aspire 專案
如需有關使用 .NET Aspire 的詳細資訊,以及應用程式範例 .AppHost
和 .ServiceDefaults
專案的詳細資料,請參閱 .NET Aspire 文件。
確認您已符合 .NET Aspire 的必要條件。 如需詳細資訊,請參閱快速入門必要條件一節:建置您的第一個 .NET Aspire 應用程式。
範例應用程式只會設定不安全的 HTTP 啟動設定檔 (http
),以在開發測試期間使用。 如需詳細資訊,包括不安全且安全的啟動設定設定檔範例,請參閱允許 .NET Aspire 中不安全的傳輸(.NET Aspire 文件)。
伺服器端 Blazor Web App 專案 (BlazorWebAppOidc
)
BlazorWebAppOidc
專案是 Blazor Web App的伺服器端專案。 專案會使用 YARP,將要求通過 Proxy 處理至後端 Web API 專案 (MinimalApiJwt
) 中的氣象預報端點,並將 access_token
儲存於驗證 cookie 中。
組態
本節說明如何設定應用程式範例。
注意
針對 Microsoft Entra ID 或 Azure AD B2C,您可以從 Microsoft AddMicrosoftIdentityWebApp Web 使用Identity(Microsoft.Identity.Web
NuGet 套件、API 檔),這會將 OIDC 和Cookie驗證處理程式新增為適當的預設值。 本節中的應用程式範例和指導不使用 Microsoft Identity Web。 這份指導示範如何為任何 OIDC 提供者手動設定 OIDC 處理常式。 如需實作 Microsoft Identity Web 的詳細資訊,請參閱連結的資源。
建立客戶端密碼
警告
請勿在用戶端程式代碼中儲存應用程式秘密、連接字串、認證、密碼、個人標識碼 (PIN)、私人 C#/.NET 程式代碼或私鑰/令牌,這一律不安全。 在測試/預備和生產環境中,伺服器端 Blazor 程序代碼和 Web API 應該使用安全驗證流程,以避免在專案程式代碼或組態檔內維護認證。 在本機開發測試之外,建議您避免使用環境變數來儲存敏感數據,因為環境變數不是最安全的方法。 針對本機開發測試, 建議使用秘密管理員工具 來保護敏感數據。 如需詳細資訊,請參閱 安全地維護敏感數據和認證。
若要進行本機開發測試,請使用 秘密管理員工具 ,將伺服器應用程式的用戶端密碼儲存在組態密鑰 Authentication:Schemes:MicrosoftOidc:ClientSecret
之下。
注意
如果應用程式使用 Microsoft Entra ID 或 Azure AD B2C,請在 Entra 或 Azure 入口網站 的應用程式中註冊中建立用戶端密碼(管理>憑證和秘密>新用戶端密碼)。 請在下列指引中使用此新密碼的值。
範例應用程式尚未為秘密管理員工具初始化。 使用命令殼層,例如 Visual Studio 中的開發人員 PowerShell 命令殼層,執行下列命令。 在執行命令之前,請將 目錄與 cd
命令變更為伺服器項目的目錄。 命令會建立使用者密碼識別碼(<UserSecretsId>
在伺服器應用程式的項目檔中):
dotnet user-secrets init
執行下列命令來設定客戶端密碼。 占位符 {SECRET}
是從應用程式註冊中獲取的用戶端密鑰。
dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"
如果使用 Visual Studio,您可以用滑鼠右鍵按兩下 方案總管 中的伺服器專案,然後選取 [管理用戶密碼],以確認秘密已設定。
設定應用程式
在呼叫 OpenIdConnectOptions 時,在專案的 Program
檔案中找到下列 AddOpenIdConnect 設定:
PushedAuthorizationBehavior:控制 PAR(推送授權要求)的支援。 在預設情況下,如果身份提供者的發現文件(通常位於
.well-known/openid-configuration
)宣布支援 PAR,則會使用 PAR。 如果您想要要求應用程式的 PAR 支援,您可以指派PushedAuthorizationBehavior.Require
的值。 Microsoft Entra 不支援 PAR,而且未來沒有 Entra 支援它的計劃。oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.UseIfAvailable;
SignInScheme:設定與在成功驗證后負責保存使用者身分識別的中間件對應的驗證配置。 OIDC 處理常式必須使用能夠跨要求保存使用者認證的登入配置。 下方這一行字僅供示範之用。 如果省略,則會使用 DefaultSignInScheme 做為備用值。
oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
openid
和profile
(Scope) 的範圍 (選擇性):預設也會設定openid
和profile
範圍,因為 OIDC 處理常式需要它們才能運作,但如果Authentication:Schemes:MicrosoftOidc:Scope
設定包含範圍,則可能必須重新新增這些範圍。 如需一般設定指導,請參閱 ASP.NET Core 中的設定和 ASP.NET Core Blazor 設定。oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);
SaveTokens:定義了在成功授權之後,是否應該將存取和重新整理權杖儲存在 AuthenticationProperties 中。 值設定為
true
,以驗證來自後端 Web API 專案 (MinimalApiJwt
) 的天氣資料要求。oidcOptions.SaveTokens = true;
離線存取範圍 (Scope):需要
offline_access
範圍才能使用刷新權杖。oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);
從 Web API 取得天氣數據的範圍(Scope):後端 Web API 專案(
MinimalApiJwt
)必須有此範圍,才能使用持有人 JWT 驗證存取令牌。oidcOptions.Scope.Add("{APP ID URI}/{API NAME}");
注意
使用 Microsoft Entra 識別符時,
Weather.Get
範圍是在 公開 API 的 Entra 或 Azure 入口網站中設定。範例:
- 應用程式識別碼 URI (
{APP ID URI}
):https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}
- 目錄名稱 (
{DIRECTORY NAME}
):contoso
- 應用程式 (用戶端) 識別碼 (
{CLIENT ID}
):00001111-aaaa-2222-bbbb-3333cccc4444
- 目錄名稱 (
- 針對來自
MinimalApiJwt
({API NAME}
) 之天氣資料所設定的範圍:Weather.Get
oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/00001111-aaaa-2222-bbbb-3333cccc4444/Weather.Get");
上述範例涉及在 AAD B2C 租用戶類型的租用戶中註冊的應用程式。 如果應用程式已在 ME-ID 租用戶註冊,則 App ID URI 不同,因此範圍不同。
範例:
- App ID URI (
{APP ID URI}
):api://{CLIENT ID}
有應用程式 (用戶端) 識別碼 ({CLIENT ID}
):00001111-aaaa-2222-bbbb-3333cccc4444
- 針對來自
MinimalApiJwt
({API NAME}
) 之天氣資料所設定的範圍:Weather.Get
oidcOptions.Scope.Add("api://00001111-aaaa-2222-bbbb-3333cccc4444/Weather.Get");
- 應用程式識別碼 URI (
Authority 和 ClientId:設定 OIDC 呼叫的授權單位和用戶端識別碼。
oidcOptions.Authority = "{AUTHORITY}"; oidcOptions.ClientId = "{CLIENT ID}";
範例:
- 授權單位 (
{AUTHORITY}
):https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/
(使用租戶 IDaaaabbbb-0000-cccc-1111-dddd2222eeee
) - 用戶端識別碼 (
{CLIENT ID}
):00001111-aaaa-2222-bbbb-3333cccc4444
oidcOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/"; oidcOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
Microsoft Azure「通用」授權單位的範例:
「Common」權限應該用於多租戶應用程式。 您也可以針對單一租用戶應用程式使用「通用」授權方式,但必須使用自訂的 IssuerValidator,如本節稍後所示。
oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0/";
- 授權單位 (
ResponseType:將 OIDC 處理常式設定為只執行授權碼流程。 在這個模式中,不需要隱含授與和混合式流程。 OIDC 處理程序使用從授權端點傳回的授權碼,自動要求適當的權杖。
oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
注意
使用 Microsoft Entra ID 時,請 不要 選取授權端點的任何一個複選框,以傳回 存取令牌 或 標識符令牌 在 隱含授與和混合式流程 應用程式註冊設定。
MapInboundClaims 以及 NameClaimType 和 RoleClaimType 的設定:許多 OIDC 伺服器使用「
name
」和「role
」,而不是在 ClaimTypes 中使用 SOAP/WS-Fed 的預設值。 MapInboundClaims 設定為false
時,處理常式不會執行宣告對應,而且應用程式會直接使用 JWT 的宣告名稱。 下列範例會將角色宣告類型設定為「roles
」,這適用於 Microsoft Entra ID (ME-ID)。 如需詳細資訊,請參閱身分識別提供者的文件。備註
針對多數 OIDC 提供者,MapInboundClaims 必須設定為
false
,以防重新命名宣告。oidcOptions.MapInboundClaims = false; oidcOptions.TokenValidationParameters.NameClaimType = "name"; oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
路徑設定:路徑必須符合向 OIDC 提供者註冊應用程式時設定的重新導向 URI (登入回呼路徑),以及登出後重新導向 (登出回呼路徑)。 在 Azure 入口網站,路徑是在應用程式註冊的驗證面板設定。 登入和登出路徑都必須註冊為重新導向 URI。 預設值是
/signin-oidc
和/signout-callback-oidc
。在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost:{PORT}/signin-oidc
注意
使用 Microsoft Entra ID 時,
localhost
位址不需要連接埠。 大部分的其他 OIDC 提供者都需要正確的埠。SignedOutCallbackPath (組態鑰匙:"
SignedOutCallbackPath
"):OIDC 處理程式攔截的要求路徑位於應用程式的基底路徑中,當使用者代理從身份提供者註銷後,會首先被返回至此處理程式。 範例應用程式不會設定路徑的值,因為會使用預設值 「/signout-callback-oidc
」。。 攔截要求之後,如果指定,OIDC 處理程式會重新導向至 SignedOutRedirectUri 或 RedirectUri。在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost:{PORT}/signout-callback-oidc
注意
使用 Microsoft Entra 識別符時,請在 Entra 或 Azure 入口網站中的 Web 平臺組態 重新導向 URI 項目中設定路徑。 使用 Entra 時,
localhost
位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。 如果您未將已登出的回呼路徑 URI 新增至 Entra 中應用程式的註冊,Entra 將拒絕將使用者重新導回應用程式,只會要求他們關閉瀏覽器視窗。RemoteSignOutPath:在這個路徑收到的要求會讓處理常式使用登出方案來叫用登出。
在下列範例中,
{PORT}
佔位符是應用程式的埠:https://localhost/signout-oidc
注意
使用 Microsoft Entra ID 時,請在 Entra 或 Azure 入口網站中設定 前端通道登出 URL。 使用 Entra 時,
localhost
位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。oidcOptions.CallbackPath = new PathString("{PATH}"); oidcOptions.SignedOutCallbackPath = new PathString("{PATH}"); oidcOptions.RemoteSignOutPath = new PathString("{PATH}");
範例 (預設值):
oidcOptions.CallbackPath = new PathString("/signin-oidc"); oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc"); oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");
(Microsoft Azure 僅使用「通用」端點) TokenValidationParameters.IssuerValidator:許多 OIDC 提供者會使用預設憑證簽發者驗證程式,但我們必須考慮以
{TENANT ID}
傳回之租用戶標識碼 (https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
) 參數化的憑證簽發者。 如需詳細資訊,請參閱 關於 OpenID Connect 和 Azure AD「通用」端點的 SecurityTokenInvalidIssuerException (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet
#1731)。僅適用於使用 Microsoft Entra ID 或 Azure AD B2C 搭配「通用」端點的應用程式:
var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority); oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;
應用程式範例的程式碼
檢查應用程式範例是否有下列功能:
- 透過自訂 cookie 刷新器 (
CookieOidcRefresher.cs
) 協助的自動無需互動令牌刷新。 - 伺服器專案會呼叫 AddAuthenticationStateSerialization 來新增伺服器端驗證狀態提供者,使用 PersistentComponentState 將驗證狀態傳送至用戶端。 用戶端會呼叫 AddAuthenticationStateDeserialization 來反序列化並使用伺服器所傳遞的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。
- 對 Blazor Web App 的要求會由 Proxy 處理至後端 Web API 專案 (
MinimalApiJwt
)。MapForwarder
檔案中的Program
使用傳出要求的預設設定、自訂的轉換和預設 HTTP 用戶端,將符合指定模式的 HTTP 要求直接轉送至特定目的地:- 在伺服器上轉譯
Weather
元件時,該元件使用ServerWeatherForecaster
類別來代理天氣數據的請求,並附上使用者的存取令牌。 IHttpContextAccessor.HttpContext 確認 HttpContext 是否能被GetWeatherForecastAsync
方法使用。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件。 - 在用戶端轉譯元件時,元件會使用
ClientWeatherForecaster
服務實作,它會使用預先設定的 HttpClient (位於用戶端專案的Program
檔案) 對伺服器專案進行 Web API 呼叫。 伺服器專案檔案中的最小 API 端點 (/weather-forecast
) 定義,將請求與使用者的存取權杖一起轉換,以取得天氣資料 (Program
)。
- 在伺服器上轉譯
- 透過自訂 cookie 刷新器 (
CookieOidcRefresher.cs
) 協助的自動無需互動令牌刷新。 -
PersistingAuthenticationStateProvider
類別 (PersistingAuthenticationStateProvider.cs
) 是伺服器端 AuthenticationStateProvider,使用 PersistentComponentState 將驗證狀態傳遞到用戶端,接著在 WebAssembly 應用程式的存留期內保持固定。 - 對 Blazor Web App 的要求會由 Proxy 處理至後端 Web API 專案 (
MinimalApiJwt
)。MapForwarder
檔案中的Program
使用傳出要求的預設設定、自訂的轉換和預設 HTTP 用戶端,將符合指定模式的 HTTP 要求直接轉送至特定目的地:- 在伺服器上轉譯
Weather
元件時,該元件使用ServerWeatherForecaster
類別來代理天氣數據的請求,並附上使用者的存取令牌。 IHttpContextAccessor.HttpContext 確認 HttpContext 是否能被GetWeatherForecastAsync
方法使用。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件。 - 在用戶端轉譯元件時,元件會使用
ClientWeatherForecaster
服務實作,它會使用預先設定的 HttpClient (位於用戶端專案的Program
檔案) 對伺服器專案進行 Web API 呼叫。 在伺服器專案的Program
檔案中定義的最小 API 端點 (/weather-forecast
) 使用使用者的存取權杖轉換要求,以取得天氣資料。
- 在伺服器上轉譯
如需在 Blazor Web App 中使用服務抽象概念呼叫 Web API 的詳細資訊,請參閱 從 ASP.NET Core Blazor 應用程式呼叫 Web API。
Blazor Web App 用戶端專案 (BlazorWebAppOidc.Client
)
BlazorWebAppOidc.Client
專案是 Blazor Web App的用戶端專案。
用戶端會呼叫 AddAuthenticationStateDeserialization 來反序列化並使用伺服器所傳遞的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。
PersistentAuthenticationStateProvider
類別 (PersistentAuthenticationStateProvider.cs
) 是一個客戶端 AuthenticationStateProvider,藉由尋找在伺服器端渲染時保存在頁面中的資料,來判斷使用者的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。
如果使用者需要登入或登出,則需要完整頁面重新載入。
應用程式範例提供使用者名稱和電子郵件僅供顯示之用。 在提出後續要求時不包含用於向伺服器驗證的權杖,而是採用一種個別運作的方式,該方式將 cookie 包含在向伺服器提出的 HttpClient 要求中。
後端 Web API 專案 (MinimalApiJwt
)
MinimalApiJwt
專案是多個前端專案的後端 Web API。 專案會為天氣資料設定最小 API 端點。 來自 Blazor Web App 伺服器端專案 (BlazorWebAppOidc
) 的要求會通過 Proxy 處理至 MinimalApiJwt
專案。
MinimalApiJwt.http
檔案可用於測試天氣資料要求。 請注意,MinimalApiJwt
專案必須執行才能測試端點,而且端點會硬式編碼到檔案中。 如需詳細資訊,請參閱在 Visual Studio 2022 中使用 .http 檔案。
組態
在專案的 JwtBearerOptions 檔案中,於 AddJwtBearer 的 Program
呼叫中設定專案:
Audience:設定任何已接收 OIDC 令牌的接受方。
jwtOptions.Audience = "{APP ID URI}";
注意
使用 Microsoft Entra ID 時,請將值與在 Entra 或 Azure 入口網站中 [公開 API] 底下新增
Weather.Get
範圍時所設定的應用程式識別碼 URI 路徑相符。範例:
應用程式識別碼 URI (
{APP ID URI}
):https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}
:- 目錄名稱 (
{DIRECTORY NAME}
):contoso
- 應用程式 (用戶端) 識別碼 (
{CLIENT ID}
):00001111-aaaa-2222-bbbb-3333cccc4444
jwtOptions.Audience = "https://contoso.onmicrosoft.com/00001111-aaaa-2222-bbbb-3333cccc4444";
上述範例涉及在 AAD B2C 租用戶類型的租用戶中註冊的應用程式。 如果應用程式已在 ME-ID 租用戶註冊,則 App ID URI 不同,因此受眾不同。
範例:
App ID URI (
{APP ID URI}
):api://{CLIENT ID}
有應用程式 (用戶端) 識別碼 ({CLIENT ID}
):00001111-aaaa-2222-bbbb-3333cccc4444
jwtOptions.Audience = "api://00001111-aaaa-2222-bbbb-3333cccc4444";
- 目錄名稱 (
Authority:設定發出 OIDC 呼叫的授權單位。 將值與
BlazorWebAppOidc/Program.cs
中為 OIDC 處理常式設定的授權單位比對:jwtOptions.Authority = "{AUTHORITY}";
範例:
授權單位 (
{AUTHORITY}
):https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/
(使用租戶 IDaaaabbbb-0000-cccc-1111-dddd2222eeee
)jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/";
上述範例涉及在 AAD B2C 租用戶類型的租用戶中註冊的應用程式。 如果應用程式已在 ME-ID 承租者中註冊,授權機構應與識別提供者所返回的 JWT 的發行者(
iss
)匹配。jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/";
天氣資料的最小 API
專案 Program
檔案中安全的氣象預報數據端點:
app.MapGet("/weather-forecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
}).RequireAuthorization();
RequireAuthorization 擴充方法需要路由定義的授權。 針對您新增至專案的任何控制器,請將 [Authorize]
屬性 新增至控制器或動作。
登出後重新導向至首頁
LogInOrOut
元件 (Layout/LogInOrOut.razor
) 會將傳回 URL (ReturnUrl
) 的隱藏欄位設定為目前的 URL(currentURL
)。 當使用者登出應用程式時,身份提供者會將使用者返回他們登出的頁面。如果使用者從安全頁面登出,則會返回相同的安全頁面,並再次經過驗證過程。 當使用者需要定期變更帳戶時,此驗證流程是合理的。
或者,使用以下 LogInOrOut
元件,該元件在註銷時不會提供傳回 URL。
Layout/LogInOrOut.razor
:
<div class="nav-item px-3">
<AuthorizeView>
<Authorized>
<form action="authentication/logout" method="post">
<AntiforgeryToken />
<button type="submit" class="nav-link">
<span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true">
</span> Logout
</button>
</form>
</Authorized>
<NotAuthorized>
<a class="nav-link" href="authentication/login">
<span class="bi bi-person-badge-nav-menu" aria-hidden="true"></span>
Login
</a>
</NotAuthorized>
</AuthorizeView>
</div>
令牌重新整理
自訂 cookie 重新整理器(CookieOidcRefresher.cs
)的實作在使用者的宣告到期時會自動更新。 目前的實作期待從令牌端點接收 ID 令牌,以換取重新整理令牌。 接著會使用此標識碼令牌中的宣告來覆寫使用者的宣告。
範例實作不包含針對重新整理令牌時,從UserInfo 端點請求宣告的程式代碼。 如需詳細資訊,請參閱 BlazorWebAppOidc AddOpenIdConnect with GetClaimsFromUserInfoEndpoint = true doesn't propogate role claims to client
(dotnet/aspnetcore
#58826)。
注意
有些身份提供者 只有在使用刷新令牌時才會返回存取令牌。
CookieOidcRefresher
可以用額外的邏輯來更新,以持續使用儲存在驗證 cookie 中的過去宣告集合,或使用存取令牌向 UserInfo 端點請求宣告。
密碼編譯 nonce
nonce 是字串值,將用戶端的會話與識別碼權杖產生關聯,以緩和重播攻擊。
如果您在驗證開發和測試期間收到 nonce 錯誤,無論對應用程式或測試使用者所做的變更有多小,都請針對每個測試回合使用新的 InPrivate/無痕瀏覽模式瀏覽器工作階段,因為過時的 cookie 資料可能導致 nonce 錯誤。 如需詳細資訊,請參閱 Cookie 與網站資料 一節。
重新整理權杖換成新的存取權杖時,不需要也不會使用 nonce。 在應用程式範例中,CookieOidcRefresher
(CookieOidcRefresher.cs
) 刻意將 OpenIdConnectProtocolValidator.RequireNonce 設定為 false
。
未向 Microsoft Entra(ME-ID)註冊的應用程式之應用角色
本節與未使用 Microsoft Entra ID (ME-ID) 身分識別提供者的應用程式有關。 如需使用 ME-ID 註冊的應用程式,請參閱 使用 Microsoft Entra(ME-ID)註冊的應用程式角色 章節。
在 TokenValidationParameters.RoleClaimType 的 OpenIdConnectOptions 中設定角色宣告類型 Program.cs
:
oidcOptions.TokenValidationParameters.RoleClaimType = "{ROLE CLAIM TYPE}";
對於許多 OIDC 身分提供者,角色宣告類型是 role
。 請檢查身分提供者的文件,以確認正確的值。
將 UserInfo
專案的 BlazorWebAppOidc.Client
類別取代為下列類別。
UserInfo.cs
:
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using System.Security.Claims;
namespace BlazorWebAppOidc.Client;
// Add properties to this class and update the server and client
// AuthenticationStateProviders to expose more information about
// the authenticated user to the client.
public sealed class UserInfo
{
public required string UserId { get; init; }
public required string Name { get; init; }
public required string[] Roles { get; init; }
public const string UserIdClaimType = "sub";
public const string NameClaimType = "name";
private const string RoleClaimType = "role";
public static UserInfo FromClaimsPrincipal(ClaimsPrincipal principal) =>
new()
{
UserId = GetRequiredClaim(principal, UserIdClaimType),
Name = GetRequiredClaim(principal, NameClaimType),
Roles = principal.FindAll(RoleClaimType).Select(c => c.Value)
.ToArray(),
};
public ClaimsPrincipal ToClaimsPrincipal() =>
new(new ClaimsIdentity(
Roles.Select(role => new Claim(RoleClaimType, role))
.Concat([
new Claim(UserIdClaimType, UserId),
new Claim(NameClaimType, Name),
]),
authenticationType: nameof(UserInfo),
nameType: NameClaimType,
roleType: RoleClaimType));
private static string GetRequiredClaim(ClaimsPrincipal principal,
string claimType) =>
principal.FindFirst(claimType)?.Value ??
throw new InvalidOperationException(
$"Could not find required '{claimType}' claim.");
}
此時, Razor 元件可以採用 角色型和原則型授權。 應用程式角色會出現在 role
宣告中,每個角色都對應一個宣告。
已向 Microsoft Entra(ME-ID)註冊的應用程式之應用角色
使用本章節的指導,針對使用 Microsoft Entra ID (ME-ID) 的應用程式實作應用程式角色、ME-ID 安全群組和 ME-ID 內建系統管理員角色。
本節所述的方法會設定 ME-ID,以在驗證 cookie 標頭中傳送群組和角色。 當使用者只是少數安全群組和角色的成員時,下列方法應該適用於大部分裝載平台,而不會發生標題太長的問題,例如,預設標題長度限制為 16 KB 的 IIS 裝載。(MaxRequestBytes
) 如果標題長度因群組或角色成員資格較高而造成問題,建議您不要遵循本節中的指導,而是實作 Microsoft Graph,以分別從 ME-ID 取得使用者的群組和角色,此方法不會擴大驗證 cookie 的大小。 如需詳細資訊,請參閱 錯誤要求 - 要求太長 - IIS 伺服器 (dotnet/aspnetcore
#57545)。
在 TokenValidationParameters.RoleClaimType 的 OpenIdConnectOptions 中設定角色宣告類型 Program.cs
。 將值設定為 roles
:
oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
雖然若沒有 ME-ID Premium 帳戶,您無法 將角色指派給群組 ,但您可以將角色指派給使用者,並使用標準 Azure 帳戶接收使用者的角色宣告。 本節中的指引不需要 ME-ID 進階帳戶。
使用預設目錄時,請依照指導將應用程式角色新增至應用程式,在權杖 (ME-ID 文件) 中接收這些角色,並設定和指派角色。 如果您未使用預設目錄,請在 Azure 入口網站 中編輯應用程式的資訊清單,以在資訊清單檔的 appRoles
輸入中,手動建立應用程式的角色。 有關詳細資訊,請參閱 設定角色宣告(ME-ID 文件)。
使用者的 Azure 安全群組在 groups
宣告中接收,而使用者的內建 ME-ID 系統管理員角色分配在 知名 ID(wids
)宣告中接收。 這兩個宣告類型的值都是 GUID。 當應用程式接收到這些宣告時,可用來在 Razor 元件中建立角色和原則的授權。
在 Azure 入口網站應用程式資訊清單中,將 groupMembershipClaims
屬性 設定為 All
。 值 All
會使 ME-ID 傳送登入使用者的所有安全性/通訊群組(groups
宣告)以及角色(wids
宣告)。 設定 groupMembershipClaims
屬性:
- 請在 Azure 入口網站開啟應用程式的註冊。
- 在側邊欄中選取管理>資訊清單。
- 尋找
groupMembershipClaims
屬性。 - 將值設定為
All
("groupMembershipClaims": "All"
)。 - 選取 [儲存] 按鈕。
將 UserInfo
專案的 BlazorWebAppOidc.Client
類別取代為下列類別。
UserInfo.cs
:
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using System.Security.Claims;
namespace BlazorWebAppOidc.Client;
// Add properties to this class and update the server and client
// AuthenticationStateProviders to expose more information about
// the authenticated user to the client.
public sealed class UserInfo
{
public required string UserId { get; init; }
public required string Name { get; init; }
public required string[] Roles { get; init; }
public required string[] Groups { get; init; }
public required string[] Wids { get; init; }
public const string UserIdClaimType = "sub";
public const string NameClaimType = "name";
private const string RoleClaimType = "roles";
private const string GroupsClaimType = "groups";
private const string WidsClaimType = "wids";
public static UserInfo FromClaimsPrincipal(ClaimsPrincipal principal) =>
new()
{
UserId = GetRequiredClaim(principal, UserIdClaimType),
Name = GetRequiredClaim(principal, NameClaimType),
Roles = principal.FindAll(RoleClaimType).Select(c => c.Value)
.ToArray(),
Groups = principal.FindAll(GroupsClaimType).Select(c => c.Value)
.ToArray(),
Wids = principal.FindAll(WidsClaimType).Select(c => c.Value)
.ToArray(),
};
public ClaimsPrincipal ToClaimsPrincipal() =>
new(new ClaimsIdentity(
Roles.Select(role => new Claim(RoleClaimType, role))
.Concat(Groups.Select(role => new Claim(GroupsClaimType, role)))
.Concat(Wids.Select(role => new Claim(WidsClaimType, role)))
.Concat([
new Claim(UserIdClaimType, UserId),
new Claim(NameClaimType, Name),
]),
authenticationType: nameof(UserInfo),
nameType: NameClaimType,
roleType: RoleClaimType));
private static string GetRequiredClaim(ClaimsPrincipal principal,
string claimType) =>
principal.FindFirst(claimType)?.Value ??
throw new InvalidOperationException(
$"Could not find required '{claimType}' claim.");
}
此時, Razor 元件可以採用 角色型和原則型授權:
- 應用程式角色會出現在
roles
宣告中,每個角色都對應一個宣告。 - 安全群組會出現在
groups
宣告中,每個群組包含一個宣告。 當建立安全群組時,安全群組 GUID 會顯示於 Azure 入口網站,並在選取 Identity>概觀>群組>檢視時列出。 - 內建 ME-ID 系統管理員角色會出現在
wids
聲明中,每個角色對應一個聲明。 具有wids
值的b79fbf4d-3ef9-4689-8143-76b194e85509
宣告一律由 ME-ID 傳送至租用戶的非訪客帳戶,且不涉及系統管理員角色。 選取 角色與系統管理員時,系統管理員角色 GUID(角色範本 ID)會出現在 Azure 入口網站中,後面接著省略符號(...) >所列角色的描述 。 角色範本 ID 也會列在 Microsoft Entra 內建角色中(Entra 文件)。
疑難排解
記錄
伺服器應用程式是標準 ASP.NET Core 應用程式。 請參閱 ASP.NET Core 記錄指導,在伺服器應用程式啟用較低的記錄層級。
若要啟用 Blazor WebAssembly 驗證的偵錯或追蹤記錄,請參閱 ASP.NET Core 日誌中「用戶端驗證日誌」的Blazor一節,並將文章版本選擇器設定為 ASP.NET Core 7.0 或更新版本。
常見錯誤
應用程式或 Identity 提供者 (IP) 的設定錯誤
最常見的錯誤是由不正確的設定所造成。 以下是一些範例:
- 視案例的需求而定,遺漏或不正確的授權單位、執行個體、租用戶識別碼、租用戶網域、用戶端識別碼或重新導向 URI 會防止應用程式驗證用戶端。
- 不正確的要求範圍會防止用戶端存取伺服器 Web API 端點。
- 不正確或遺漏伺服器 API 權限會防止用戶端存取伺服器 Web API 端點。
- 在不同於 IP 應用程式註冊中重定向 URI 所設定的連接埠上運行應用程式。 請注意,Microsoft Entra ID 和在
localhost
開發測試位址執行的應用程式不需要連接埠,但應用程式的連接埠設定和執行應用程式的連接埠必須與非localhost
位址相符。
本文中的設定範圍顯示正確配置的範例。 請仔細查看設定,確定應用程式和 IP 是否設定錯誤。
如果設定顯示正確:
分析應用程式記錄檔。
使用瀏覽器的開發人員工具,檢查用戶端應用程式與 IP 或伺服器應用程式之間的網路流量。 通常,在提出要求之後,IP 或伺服器應用程式會傳回錯誤訊息或有導致問題的線索訊息給用戶端。 下列文章中可找到開發人員工具指導:
- Google Chrome (Google 文件)
- Microsoft Edge
- Mozilla Firefox (Mozilla 文件)
文件小組會回應文章中的文件回饋和錯誤(從此頁面意見反應區段開啟問題),但是無法提供產品支援服務。 有數個公用支援論壇可用來協助針對應用程式進行疑難排解。 我們建議下列事項:
上述論壇並非由 Microsoft 擁有或控制。
針對非安全性、非敏感性和非機密可重現架構 BUG 報告,向 ASP.NET Core 產品單位提出問題。 在您徹底調查問題的原因而且無法自行解決,並取得公用支援論壇上社群的協助之前,請勿向產品單位提出問題。 產品單位無法針對因簡單設定錯誤或涉及第三方服務的使用案例而中斷的個別應用程式進行疑難排解。 如果報告具有敏感性或機密性質,或描述了攻擊者可能惡意探索的產品中潛在的安全性缺陷,請參閱 報告安全性問題和 BUG (
dotnet/aspnetcore
GitHub 存放庫)。ME-ID 未經授權的客戶端
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] 授權失敗。 不符合以下需求:「DenyAnonymousAuthorizationRequirement」需要已驗證的使用者。
ME-ID 的登入回呼錯誤:
- 錯誤:
unauthorized_client
- 描述:
AADB2C90058: The provided application is not configured to allow public clients.
若要解決此錯誤:
- 在 Azure 入口網站中,存取應用程式的資訊清單。
- 將
allowPublicClient
屬性設定為null
或true
。
- 錯誤:
Cookie 和網站資料
Cookie 和網站資料可以在應用程式更新之間保存,並可介入測試和疑難排解。 進行應用程式程式碼變更、使用提供者的使用者帳戶變更,或提供者應用程式設定變更時,請清除下列內容:
- 使用者登入 Cookie
- 應用程式 Cookie
- 已快取和儲存的網站資料
避免殘留的 cookie 和網站資料干擾測試和故障排除的一種方法是:
- 設定瀏覽器
- 使用瀏覽器進行測試,您可以設定在每次關閉瀏覽器時刪除所有 cookie 和網站資料。
- 請確定瀏覽器已手動關閉或由 IDE 關閉,以便對應用程式、測試使用者或提供者設定進行任何變更。
- 使用自訂命令,在 Visual Studio 中以私人模式或無痕模式開啟瀏覽器:
- 從 Visual Studio 的執行按鈕開啟瀏覽方式對話方塊。
- 選取新增按鈕。
- 在 [程式] 欄位中提供瀏覽器的路徑。 下列可執行檔路徑是 Windows 10 的一般安裝位置。 如果您的瀏覽器安裝在不同的位置,或您不是使用 Windows 10,請提供瀏覽器可執行檔的路徑。
- Microsoft Edge:
C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
- Google Chrome:
C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
- Mozilla Firefox:
C:\Program Files\Mozilla Firefox\firefox.exe
- Microsoft Edge:
- 在 [引數] 欄位中,提供瀏覽器用來在 InPrivate 模式或無痕模式中開啟的命令行選項。 某些瀏覽器需要應用程式的 URL。
- Microsoft Edge: 使用
-inprivate
。 - Google Chrome:使用
--incognito --new-window {URL}
,其中{URL}
的佔位符是要開啟的 URL(例如https://localhost:5001
)。 - Mozilla Firefox:使用
-private -url {URL}
,其中{URL}
佔位符是要開啟的 URL(例如https://localhost:5001
)。
- Microsoft Edge: 使用
- 在 [自訂名稱] 欄位中提供名稱。 例如:
Firefox Auth Testing
。 - 選取確定按鈕。
- 若要避免針對使用應用程式測試的每個反覆項目選取瀏覽器設定檔,請使用 [設為預設值] 按鈕,將設定檔設定為預設值。
- 請確定瀏覽器已由 IDE 關閉,以便對應用程式、測試使用者或提供者設定進行任何變更。
應用程式升級
在升級開發電腦上的 .NET Core SDK 或變更應用程式內的套件版本之後,正常運作的應用程式便立即發生失敗。 在某些情況下,執行主要升級時,不一致的套件可能會中斷應用程式。 大多數這些問題都可依照下列指示來進行修正:
- 從命令提示字元執行
dotnet nuget locals all --clear
以清除本機系統的 NuGet 套件快取。 - 刪除專案的
bin
和obj
資料夾。 - 還原並重建專案。
- 在重新部署應用程式之前,請先刪除伺服器上部署資料夾中的所有檔案。
注意
不支援使用與應用程式目標框架不相容的套件版本。 如需套件的相關資訊,請使用 NuGet Gallery。
執行伺服器應用程式
測試 Blazor Web App並且進行疑難排解時,請確定您是從伺服器專案執行應用程式。
檢查使用者
下列 UserClaims
元件可以直接在應用程式中使用,或作為進一步自訂的基礎。
UserClaims.razor
:
@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
<PageTitle>User Claims</PageTitle>
<h1>User Claims</h1>
@if (claims.Any())
{
<ul>
@foreach (var claim in claims)
{
<li><b>@claim.Type:</b> @claim.Value</li>
}
</ul>
}
@code {
private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();
[CascadingParameter]
private Task<AuthenticationState>? AuthState { get; set; }
protected override async Task OnInitializedAsync()
{
if (AuthState == null)
{
return;
}
var authState = await AuthState;
claims = authState.User.Claims;
}
}
其他資源
-
AzureAD/microsoft-identity-web
GitHub 存放庫:實作 Microsoft Identity Web for Microsoft Entra ID 和 Azure Active Directory B2C for ASP.NET Core 應用程式的實用指導,包括應用程式範例和相關 Azure 文件的連結。 目前,Blazor Web App 並未在 Azure 文件中被清楚說明,但適用於 ME-ID 及 Azure 託管之 Blazor Web App 的安裝和設定,與任何 ASP.NET Core Web 應用程式相同。 -
AuthenticationStateProvider
service - 在Blazor Web App中管理驗證狀態
-
使用 OIDC 在 Blazor Interactive Server 中的 HTTP 請求期間重新整理令牌 (
dotnet/aspnetcore
#55213) - 透過互動式自動渲染保護 Blazor Web App中的數據