共用方式為


使用 OpenID Connect (OIDC) 保護 ASP.NET Core Blazor Web App

Note

這不是這篇文章的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本

本文說明如何使用 Blazor Web App 和 GitHub 存放庫 (dotnet/blazor-samples) 中的應用程式範例,在 上進行保護 (.NET 8 或更新版本) (如何下載)。

針對 Microsoft Entra ID 或 Azure AD B2C,您可以使用來自 AddMicrosoftIdentityWebApp 的 Identity(Microsoft.Identity.Web),這將以適當的預設值新增 OIDC 和 認證處理程式。 本文中的範例應用程式和指引不會使用 Microsoft Identity Web。 這份指導示範如何為任何 OIDC 提供者手動設定 OIDC 處理常式。 如需實作 Microsoft Identity Web 的詳細資訊,請參閱使用 Microsoft Entra 標識符保護 ASP.NET CoreBlazor Web App

本文的這個版本涵蓋了實作 OIDC,而不採用 前端後端 (BFF) 模式,並且採用全域互動式自動呈現的應用程式(包括伺服器和 .Client 專案)。 BFF 模式適用於對外部服務提出已驗證的要求。 如果應用程式的規格要求採用 BFF 模式,請將發行項版本選取器變更為 BFF 模式

採用下列規格:

  • Blazor Web App 使用 自動轉譯模式搭配全域互動功能
  • 伺服器和用戶端應用程式使用自訂驗證狀態提供者服務擷取使用者的驗證狀態,並在伺服器與客戶端之間流動。
  • 這個應用程式是任何 OIDC 驗證流程的起點。 OIDC 在應用程式手動設定,且不採用 Microsoft Entra IDMicrosoft Identity Web 套件,應用程式範例也不需要 Microsoft Azure 裝載。 不過,應用程式範例可與 Entra、Microsoft Identity Web 搭配使用,並且裝載於 Azure。
  • 自動非互動式令牌刷新。
  • 獨立的 Web API 專案展示了針對氣象資料的安全 Web API 呼叫。

如需使用適用於 .NET的 Microsoft 驗證連結庫、Microsoft WebMicrosoft Entra ID的替代體驗,請參閱 使用 Microsoft Entra ID保護 ASP.NET Core

範例解決方案

範例應用程式包含下列專案:

  • BlazorWebAppOidc: Blazor Web App的伺服器端專案,包含天氣資料的 最小 API 端點範例。
  • BlazorWebAppOidc.Client: Blazor Web App的用戶端專案。
  • MinimalApiJwt:使用簡化 API 端點提供天氣數據的後端 Web API。

使用下列連結,透過 Blazor 範例存放庫中的最新版本資料夾存取範例。 此範例位於 BlazorWebAppOidc .NET 8 或更新版本的資料夾中。

Aspire/Aspire.AppHost 項目 啟動解決方案。

檢視或下載範例程式碼 \(英文\) (如何下載)

範例解決方案功能:

  • 透過自訂 cookie 刷新器 (CookieOidcRefresher.cs) 協助的自動無需互動令牌刷新。

  • /weather-forecast 專案的Program 檔案中,天氣數據由一個最小 API 端點 Program.cs 處理。 端點需要藉由呼叫 RequireAuthorization 來進行授權。 針對您新增至專案的任何控制器,請將 [Authorize] 屬性 新增至控制器或動作。 如需要求應用程式內跨系統授權的 授權原則 的詳細資訊,以及如何在部分公用端點選擇退出授權,請參閱 Razor Pages OIDC 指引

  • 應用程式會安全地呼叫 Web API 以取得天氣資料:

    • 在伺服器上渲染Weather元件時,該元件使用伺服器上的ServerWeatherForecaster來透過MinimalApiJwtDelegatingHandler)從TokenHandler專案中的 Web API 獲取天氣數據,此過程會將存取令牌從HttpContext附加到請求中。
    • 當元件在用戶端呈現時,會使用 ClientWeatherForecaster 服務實作。該實作利用預先設定的 HttpClient,此設定位於用戶端專案的 Program 檔案中,以從伺服器專案的 ServerWeatherForecaster 發起 Web API 呼叫。
  • PersistingAuthenticationStateProvider 類別 (PersistingAuthenticationStateProvider.cs) 是伺服器端 AuthenticationStateProvider,使用 PersistentComponentState 將驗證狀態傳遞到用戶端,接著在 WebAssembly 應用程式的存留期內保持固定。

如需在 Blazor Web App 中使用服務抽象概念呼叫 Web API 的詳細資訊,請參閱 從 ASP.NET Core Blazor 應用程式呼叫 Web API

OIDC 提供者術語和指引

雖然您不需要採用 Microsoft Entra (ME-ID) 作為 OIDC 提供者,才能使用本文中的範例應用程式和指引,但本文說明使用Microsoft檔和 Azure/Entra 入口網站中找到的名稱 ME-ID 設定。 OIDC 設定在 OIDC 提供者之間具有類似的命名。 使用第三方 OIDC 提供者時,請將提供者的檔與本文中的應用程式和 Web API 註冊指引搭配使用。

Microsoft Entra ID 應用程式註冊

我們建議針對應用程式和 Web API 使用不同的註冊,即使應用程式和 Web API 位於相同的解決方案中也一樣。 下列指引適用於 BlazorWebAppOidc 範例解決方案的應用程式和 MinimalApiJwt Web API,但相同的指導方針通常適用於應用程式和 Web API 的任何以 Entra 為基礎的註冊。

如需應用程式和 Web API 註冊指引,請參閱 在 Microsoft Entra ID 中註冊應用程式

先註冊 Web API (MinimalApiJwt),讓您可以在註冊應用程式時授與 Web API 的存取權。 Web API 的租用戶標識碼和用戶端識別碼可用來在其 Program 檔案中設定 Web API。 註冊 Web API 之後,請在應用程式註冊>中公開 WebAPI 公開具有 範圍名稱的 Weather.GetAPI。 記錄應用程式識別碼 URI 以用於應用程式的組態。

接下來,使用 BlazorWebAppOidc 平台組態,並將應用程式 /BlazorWebApOidc.Client 註冊到重定向 URIhttps://localhost/signin-oidc(不需要埠)。 應用程式的租使用者識別碼和用戶端標識碼,以及 Web API 的基地址、應用程式識別碼 URI 和天氣範圍名稱,可用來在其 Program 檔案中設定應用程式。 授與 API 許可權,以存取 應用程式註冊>API 許可權中的 Web API。 如果應用程式的安全性規格要求它,您可以授與系統管理員同意,讓組織存取 Web API。 授權的使用者和群組會指派給應用程式註冊>的應用程式註冊

在 Entra 或 Azure 入口網站的 隱含授與和混合式流程 應用程式註冊設定中,請勿選取授權端點的複選框以傳回 存取令牌標識碼令牌。 OpenID Connect 處理程式會使用從授權端點傳回的程式代碼,自動要求適當的令牌。

在 Entra 或 Azure 入口網站的應用程式註冊中建立客戶端密碼(管理>憑證和秘密>新客戶端密碼)。 請保留用戶端秘密 ,以使用下一節。

本文稍後會提供特定設定的其他 Entra 設定指引。

建立客戶端密碼

本節僅適用於 (Blazor Web App專案) 的伺服器BlazorWebAppOidc專案。

Warning

請勿在用戶端程式代碼中儲存應用程式秘密、連接字串、認證、密碼、個人標識碼 (PIN)、私人 C#/.NET 程式代碼或私鑰/令牌,這一律不安全。 在測試/預備和生產環境中,伺服器端 Blazor 程序代碼和 Web API 應該使用安全驗證流程,以避免在專案程式代碼或組態檔內維護認證。 在本機開發測試之外,建議您避免使用環境變數來儲存敏感數據,因為環境變數不是最安全的方法。 針對本機開發測試, 建議使用秘密管理員工具 來保護敏感數據。 如需詳細資訊,請參閱 安全地維護敏感數據和認證

若要進行本機開發測試,請使用 秘密管理員工具 ,將伺服器專案的用戶端密碼儲存 Blazor 在組態密鑰 Authentication:Schemes:MicrosoftOidc:ClientSecret之下。

伺服器專案尚未為秘密管理員工具初始化。 使用命令殼層,例如 Visual Studio 中的開發人員 PowerShell 命令殼層,執行下列命令。 在執行命令之前,請將 目錄與 cd 命令變更為伺服器項目的目錄。 命令會建立使用者密碼識別碼(<UserSecretsId> 在伺服器應用程式的項目檔中):

dotnet user-secrets init

執行下列命令來設定客戶端密碼。 占位符 {SECRET} 是從應用程式註冊中獲取的用戶端密鑰。

dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"

如果使用 Visual Studio,您可以用滑鼠右鍵按兩下 方案總管 中的伺服器專案,然後選取 [管理用戶密碼],以確認秘密已設定。

MinimalApiJwt 專案

MinimalApiJwt 專案是多個前端專案的後端 Web API。 專案會為天氣資料設定最小 API 端點。

MinimalApiJwt.http 檔案可用於測試天氣資料要求。 請注意,MinimalApiJwt 專案必須執行才能測試端點,而且端點會硬式編碼到檔案中。 如需詳細資訊,請參閱在 Visual Studio 2022 中使用 .http 檔案

專案包含用來產生 OpenAPI 檔的套件和配置。

專案包含套件和元件,可在開發環境中產生OpenAPI檔和Swagger UI。 如需詳細資訊,請參閱 使用產生的OpenAPI檔

專案會建立天氣數據的 最小 API 端點:

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

在專案的 JwtBearerOptions 檔案中,於 AddJwtBearer 呼叫中的 Program 進行專案設定。

Authority 設定 OIDC 呼叫的 Authority。 我們建議針對 MinimalApiJwt 專案使用不同的應用程式註冊。 授權機構與身分提供者所傳回的 JWT 的發行者(iss)相匹配。

jwtOptions.Authority = "{AUTHORITY}";

授權格式取決於使用中的租戶類型。 下列 Microsoft Entra ID 的範例會使用租用戶 ID aaaabbbb-0000-cccc-1111-dddd2222eeee

ME-ID 租戶權限範例:

jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee";

AAD B2C 租戶授權單位範例:

jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";

Audience 用於指定任何收到的 OIDC 令牌的受眾。

jwtOptions.Audience = "{APP ID URI}";

Note

使用 Microsoft Entra ID 時,請將值與在 Entra 或 Azure 入口網站中 [公開 API] 底下新增Weather.Get範圍時所設定的應用程式識別碼 URI 路徑相符。 請勿在值中包含範圍名稱 "Weather.Get"。

受眾的格式取決於正在使用的租用戶類型。 下列 Microsoft Entra ID 的範例中使用了租用戶識別碼 contoso 和用戶端識別碼 11112222-bbbb-3333-cccc-4444dddd5555

ME-ID 租戶應用程式識別碼 URI 範例:

jwtOptions.Audience = "api://11112222-bbbb-3333-cccc-4444dddd5555";

AAD B2C 租使用者應用程式識別碼 URI 範例:

jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555";

Blazor Web App 伺服器專案 (BlazorWebAppOidc

BlazorWebAppOidc 專案是 Blazor Web App的伺服器端專案。

DelegatingHandlerTokenHandler)用於管理將使用者的存取令牌附加至傳出請求。 令牌處理程式只會在靜態伺服器端轉譯期間執行(靜態 SSR),因此 HttpContext 在此案例中使用 是安全的。 如需詳細資訊,請參閱 ASP.NET Core 應用程式中的 IHttpContextAccessor/HttpContextBlazor ASP.NET Core 伺服器端和其他安全性案例Blazor Web App

TokenHandler.cs

public class TokenHandler(IHttpContextAccessor httpContextAccessor) : 
    DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw new Exception("HttpContext not available");
        }

        var accessToken = await httpContextAccessor.HttpContext
            .GetTokenAsync("access_token");

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);

        return await base.SendAsync(request, cancellationToken);
    }
}

在專案的檔案中Program,令牌處理程式 (TokenHandler) 會註冊為服務,並指定為訊息處理程式AddHttpMessageHandler,以便使用MinimalApiJwt對後端 Web API 提出安全要求(“)。ExternalApi

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("ExternalApi",
      client => client.BaseAddress = new Uri(builder.Configuration["ExternalApiUri"] ?? 
          throw new Exception("Missing base address!")))
      .AddHttpMessageHandler<TokenHandler>();

在項目的檔案中 appsettings.json ,設定外部 API URI:

"ExternalApiUri": "{BASE ADDRESS}"

Example:

"ExternalApiUri": "https://localhost:7277"

在呼叫 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;

openidprofile (Scope) 的範圍 (選擇性):預設也會設定 openidprofile 範圍,因為 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);

AuthorityClientId:設定 OIDC 呼叫的授權單位和用戶端識別碼。

oidcOptions.Authority = "{AUTHORITY}";
oidcOptions.ClientId = "{CLIENT ID}";

下列範例使用 的 aaaabbbb-0000-cccc-1111-dddd2222eeee 租使用者識別碼和 的 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";

針對多租戶應用程式,應該使用「common」授權。 您也可以針對單一租用戶應用程式使用「通用」授權方式,但必須使用自訂的 IssuerValidator,如本節稍後所示。

oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0";

ResponseType:將 OIDC 處理常式設定為只執行授權碼流程。 在這個模式中,不需要隱含授與和混合式流程。 OIDC 處理程序使用從授權端點傳回的授權碼,自動要求適當的權杖。

oidcOptions.ResponseType = OpenIdConnectResponseType.Code;

MapInboundClaimsNameClaimTypeRoleClaimType 的設定:許多 OIDC 伺服器在 name 使用 “role” 和 “ClaimTypes”,而不是使用 SOAP/WS-Fed 預設值。 當 MapInboundClaims 設定為 false時,處理常式不會執行宣告對應,而且應用程式會直接使用 JWT 的宣告名稱。 下列範例會將角色宣告類型設定為「roles」,這適用於 Microsoft Entra ID (ME-ID)。 如需詳細資訊,請參閱身分識別提供者的文件。

Note

針對多數 OIDC 提供者,MapInboundClaims 必須設定為 false,以防重新命名宣告。

oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters.NameClaimType = "name";
oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

路徑設定:路徑必須符合向 OIDC 提供者註冊應用程式時設定的重新導向 URI (登入回呼路徑),以及登出後重新導向 (登出回呼路徑)。 在 Azure 入口網站,路徑是在應用程式註冊的驗證面板設定。 登入和登出路徑都必須註冊為重新導向 URI。 預設值是 /signin-oidc/signout-callback-oidc

CallbackPath:應用程式基本路徑中用於返回使用者代理的請求路徑。

在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,{PORT} 佔位符是應用程式的埠:

https://localhost:{PORT}/signin-oidc

Note

使用 Microsoft Entra ID 時,localhost 位址不需要連接埠。 大部分的其他 OIDC 提供者都需要正確的埠。

SignedOutCallbackPath (組態鑰匙:"SignedOutCallbackPath"):OIDC 處理程式攔截的要求路徑位於應用程式的基底路徑中,當使用者代理從身份提供者註銷後,會首先被返回至此處理程式。 範例應用程式不會設定路徑的值,因為會使用預設值 「/signout-callback-oidc」。。 攔截要求之後,如果指定,OIDC 處理程式會重新導向至 SignedOutRedirectUriRedirectUri

在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,{PORT} 佔位符是應用程式的埠:

https://localhost:{PORT}/signout-callback-oidc

Note

使用 Microsoft Entra 識別符時,請在 Entra 或 Azure 入口網站中的 Web 平臺組態 重新導向 URI 項目中設定路徑。 使用 Entra 時,localhost 位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。 如果您未將已登出的回呼路徑 URI 新增至 Entra 中應用程式的註冊,Entra 將拒絕將使用者重新導回應用程式,只會要求他們關閉瀏覽器視窗。

RemoteSignOutPath:在這個路徑收到的要求會讓處理常式使用登出方案來叫用登出。

在下列範例中,{PORT} 佔位符是應用程式的埠:

https://localhost/signout-oidc

Note

使用 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;

Blazor Web App 用戶端專案 (BlazorWebAppOidc.Client

BlazorWebAppOidc.Client 專案是 Blazor Web App的用戶端專案。

用戶端會呼叫 AddAuthenticationStateDeserialization 來反序列化並使用伺服器所傳遞的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。

PersistentAuthenticationStateProvider 類別 (PersistentAuthenticationStateProvider.cs) 是一個客戶端 AuthenticationStateProvider,藉由尋找在伺服器端渲染時保存在頁面中的資料,來判斷使用者的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。

如果使用者需要登入或登出,則需要完整頁面重新載入。

應用程式範例提供使用者名稱和電子郵件僅供顯示之用。

針對 Microsoft Entra ID 或 Azure AD B2C,您可以使用來自 AddMicrosoftIdentityWebApp 的 Identity(Microsoft.Identity.Web),這將以適當的預設值新增 OIDC 和 認證處理程式。 本文中的範例應用程式和指引不會使用 Microsoft Identity Web。 這份指導示範如何為任何 OIDC 提供者手動設定 OIDC 處理常式。 如需實作 Microsoft Identity Web 的詳細資訊,請參閱使用 Microsoft Entra 標識符保護 ASP.NET CoreBlazor Web App

本文的這個版本涵蓋實作 OIDC,並且未採用 前端後端(BFF)模式 的應用程式,而是採用全域互動式伺服器渲染(單一專案)。 BFF 模式適用於對外部服務提出已驗證的要求。 如果應用程式的規格要求採用具有全域互動式自動轉譯的 BFF 模式 ,請將發行項版本選取器變更為 BFF 模式。

採用下列規格:

  • Blazor Web App 使用 伺服器渲染模式與全域互動
  • 這個應用程式是任何 OIDC 驗證流程的起點。 OIDC 在應用程式手動設定,且不採用 Microsoft Entra IDMicrosoft Identity Web 套件,應用程式範例也不需要 Microsoft Azure 裝載。 不過,應用程式範例可與 Entra、Microsoft Identity Web 搭配使用,並且裝載於 Azure。
  • 自動非互動式令牌刷新。
  • 獨立的 Web API 專案展示了針對氣象資料的安全 Web API 呼叫。

如需使用適用於 .NET的 Microsoft 驗證連結庫、Microsoft WebMicrosoft Entra ID的替代體驗,請參閱 使用 Microsoft Entra ID保護 ASP.NET Core

範例解決方案

範例應用程式包含下列專案:

  • BlazorWebAppOidcServer: Blazor Web App 伺服器端專案(全域互動式伺服器轉譯)。
  • MinimalApiJwt:使用簡化 API 端點提供天氣數據的後端 Web API。

使用下列連結,透過 Blazor 範例存放庫中的最新版本資料夾存取範例。 此範例位於 BlazorWebAppOidcServer .NET 8 或更新版本的資料夾中。

檢視或下載範例程式碼 \(英文\) (如何下載)

Microsoft Entra ID 應用程式註冊

我們建議針對應用程式和 Web API 使用不同的註冊,即使應用程式和 Web API 位於相同的解決方案中也一樣。 下列指引適用於 BlazorWebAppOidcServer 範例解決方案的應用程式和 MinimalApiJwt Web API,但相同的指導方針通常適用於應用程式和 Web API 的任何以 Entra 為基礎的註冊。

先註冊 Web API (MinimalApiJwt),讓您可以在註冊應用程式時授與 Web API 的存取權。 Web API 的租用戶標識碼和用戶端識別碼可用來在其 Program 檔案中設定 Web API。 註冊 Web API 之後,請在應用程式註冊>中公開 WebAPI 公開具有 範圍名稱的 Weather.GetAPI。 記錄應用程式識別碼 URI 以用於應用程式的組態。

接下來,使用 BlazorWebAppOidcServer 平台組態和 重新導向 URI 註冊應用程式https://localhost/signin-oidc (不需要埠)。 應用程式的租使用者識別碼和用戶端標識碼,以及 Web API 的基地址、應用程式識別碼 URI 和天氣範圍名稱,可用來在其 Program 檔案中設定應用程式。 授與 API 許可權,以存取 應用程式註冊>API 許可權中的 Web API。 如果應用程式的安全性規格要求它,您可以授與系統管理員同意,讓組織存取 Web API。 授權的使用者和群組會指派給應用程式註冊>的應用程式註冊

在 Entra 或 Azure 入口網站的 隱含授與和混合式流程 應用程式註冊設定中,請勿選取授權端點的複選框以傳回 存取令牌標識碼令牌。 OpenID Connect 處理程式會使用從授權端點傳回的程式代碼,自動要求適當的令牌。

在 Entra 或 Azure 入口網站的應用程式註冊中建立客戶端密碼(管理>憑證和秘密>新客戶端密碼)。 請保留用戶端秘密 ,以使用下一節。

本文稍後會提供特定設定的其他 Entra 設定指引。

建立客戶端密碼

本節僅適用於 (Blazor Web App專案) 的伺服器BlazorWebAppOidcServer專案。

Warning

請勿在用戶端程式代碼中儲存應用程式秘密、連接字串、認證、密碼、個人標識碼 (PIN)、私人 C#/.NET 程式代碼或私鑰/令牌,這一律不安全。 在測試/預備和生產環境中,伺服器端 Blazor 程序代碼和 Web API 應該使用安全驗證流程,以避免在專案程式代碼或組態檔內維護認證。 在本機開發測試之外,建議您避免使用環境變數來儲存敏感數據,因為環境變數不是最安全的方法。 針對本機開發測試, 建議使用秘密管理員工具 來保護敏感數據。 如需詳細資訊,請參閱 安全地維護敏感數據和認證

若要進行本機開發測試,請使用 秘密管理員工具 ,將伺服器專案的用戶端密碼儲存 Blazor 在組態密鑰 Authentication:Schemes:MicrosoftOidc:ClientSecret之下。

伺服器專案尚未為秘密管理員工具初始化。 使用命令殼層,例如 Visual Studio 中的開發人員 PowerShell 命令殼層,執行下列命令。 在執行命令之前,請將 目錄與 cd 命令變更為伺服器項目的目錄。 命令會建立使用者密碼識別碼(<UserSecretsId> 在應用程式的項目檔中):

dotnet user-secrets init

執行下列命令來設定客戶端密碼。 占位符 {SECRET} 是從應用程式註冊中獲取的用戶端密鑰。

dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"

如果使用 Visual Studio,您可以在 [方案總管] 中以滑鼠右鍵按一下專案,然後選取 [管理使用者密碼]來確認秘密是否已設定。

MinimalApiJwt 專案

MinimalApiJwt 專案是多個前端專案的後端 Web API。 專案會為天氣資料設定最小 API 端點。

MinimalApiJwt.http 檔案可用於測試天氣資料要求。 請注意,MinimalApiJwt 專案必須執行才能測試端點,而且端點會硬式編碼到檔案中。 如需詳細資訊,請參閱在 Visual Studio 2022 中使用 .http 檔案

專案包含用來產生 OpenAPI 檔的套件和配置。

專案包含套件和元件,可在開發環境中產生OpenAPI檔和Swagger UI。 如需詳細資訊,請參閱 使用產生的OpenAPI檔

專案會建立天氣數據的 最小 API 端點:

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

在專案的 JwtBearerOptions 檔案中,於 AddJwtBearer 呼叫中的 Program 進行專案設定。

Authority 設定 OIDC 呼叫的 Authority。 我們建議針對 MinimalApiJwt 專案使用不同的應用程式註冊。 授權機構與身分提供者所傳回的 JWT 的發行者(iss)相匹配。

jwtOptions.Authority = "{AUTHORITY}";

授權格式取決於使用中的租戶類型。 下列 Microsoft Entra ID 的範例會使用租用戶 ID aaaabbbb-0000-cccc-1111-dddd2222eeee

ME-ID 租戶權限範例:

jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee";

AAD B2C 租戶授權單位範例:

jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";

Audience 用於指定任何收到的 OIDC 令牌的受眾。

jwtOptions.Audience = "{APP ID URI}";

Note

使用 Microsoft Entra ID 時,請將值與在 Entra 或 Azure 入口網站中 [公開 API] 底下新增Weather.Get範圍時所設定的應用程式識別碼 URI 路徑相符。 請勿在值中包含範圍名稱 "Weather.Get"。

受眾的格式取決於正在使用的租用戶類型。 下列 Microsoft Entra ID 的範例中使用了租用戶識別碼 contoso 和用戶端識別碼 11112222-bbbb-3333-cccc-4444dddd5555

ME-ID 租戶應用程式識別碼 URI 範例:

jwtOptions.Audience = "api://11112222-bbbb-3333-cccc-4444dddd5555";

AAD B2C 租使用者應用程式識別碼 URI 範例:

jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555";

BlazorWebAppOidcServer 專案

自動非互動式的令牌更新由自定義的 cookie 更新器 CookieOidcRefresher.cs 來管理。

DelegatingHandlerTokenHandler)用於管理將使用者的存取令牌附加至傳出請求。 令牌處理程式只會在靜態伺服器端轉譯期間執行(靜態 SSR),因此 HttpContext 在此案例中使用 是安全的。 如需詳細資訊,請參閱 ASP.NET Core 應用程式中的 IHttpContextAccessor/HttpContextBlazor ASP.NET Core 伺服器端和其他安全性案例Blazor Web App

TokenHandler.cs

public class TokenHandler(IHttpContextAccessor httpContextAccessor) : 
    DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw new Exception("HttpContext not available");
        }

        var accessToken = await httpContextAccessor.HttpContext
            .GetTokenAsync("access_token");

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);

        return await base.SendAsync(request, cancellationToken);
    }
}

在專案的檔案中Program,令牌處理程式 (TokenHandler) 會註冊為服務,並指定為訊息處理程式AddHttpMessageHandler,以便使用MinimalApiJwt對後端 Web API 提出安全要求(“)。ExternalApi

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("ExternalApi",
      client => client.BaseAddress = new Uri(builder.Configuration["ExternalApiUri"] ?? 
          throw new Exception("Missing base address!")))
      .AddHttpMessageHandler<TokenHandler>();

Weather 元件會使用 [Authorize] 屬性 來防止未經授權的存取。 如需要求應用程式內跨系統授權的 授權原則 的詳細資訊,以及如何在部分公用端點選擇退出授權,請參閱 Razor Pages OIDC 指引

ExternalApi HTTP 用戶端可用來向安全的 Web API 提出天氣數據的要求。 在 OnInitializedAsync 的生命週期事件Weather.razor

using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast");
var client = ClientFactory.CreateClient("ExternalApi");
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();

forecasts = await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ??
    throw new IOException("No weather forecast!");

在項目的檔案中 appsettings.json ,設定外部 API URI:

"ExternalApiUri": "{BASE ADDRESS}"

Example:

"ExternalApiUri": "https://localhost:7277"

在呼叫 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;

openidprofile (Scope) 的範圍 (選擇性):預設也會設定 openidprofile 範圍,因為 OIDC 處理常式需要它們才能運作,但如果 Authentication:Schemes:MicrosoftOidc:Scope 設定包含範圍,則可能必須重新新增這些範圍。 如需一般設定指導,請參閱 ASP.NET Core 中的設定ASP.NET Core Blazor 設定

oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);

設定通過 Weather.Get 來訪問天氣數據的外部 Web API 的範圍。 下列範例是以在 ME-ID 租用戶網域中使用 Entra ID 為基礎。 在下列範例中, {APP ID URI} 佔位符位於公開 Web API 的 Entra 或 Azure 入口網站中。 針對任何其他識別提供者,請使用適當的範圍。

oidcOptions.Scope.Add("{APP ID URI}/Weather.Get");

使用範圍的格式取決於租用戶的型態。 在下列範例中,租使用者網域為 contoso.onmicrosoft.com,而用戶端識別碼為 11112222-bbbb-3333-cccc-4444dddd5555

ME-ID 租戶應用程式識別碼 URI 範例:

oidcOptions.Scope.Add("api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get");

AAD B2C 租使用者應用程式識別碼 URI 範例:

oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get");

SaveTokens:定義了在成功授權之後,是否應該將存取和重新整理權杖儲存在 AuthenticationProperties 中。 此屬性會設定為 true,以便將重新整理令牌儲存起來,用於非互動式的令牌重新整理過程。

oidcOptions.SaveTokens = true;

離線存取所需的範圍 (Scope):offline_access 範圍對重新整理權杖是必要的。

oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);

AuthorityClientId:設定 OIDC 呼叫的授權單位和用戶端識別碼。

oidcOptions.Authority = "{AUTHORITY}";
oidcOptions.ClientId = "{CLIENT ID}";

下列範例使用 的 aaaabbbb-0000-cccc-1111-dddd2222eeee 租使用者識別碼和 的 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";

針對多租戶應用程式,應該使用「common」授權。 您也可以針對單一租用戶應用程式使用「通用」授權方式,但必須使用自訂的 IssuerValidator,如本節稍後所示。

oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0";

ResponseType:將 OIDC 處理常式設定為只執行授權碼流程。 在這個模式中,不需要隱含授與和混合式流程。 OIDC 處理程序使用從授權端點傳回的授權碼,自動要求適當的權杖。

oidcOptions.ResponseType = OpenIdConnectResponseType.Code;

MapInboundClaimsNameClaimTypeRoleClaimType 的設定:許多 OIDC 伺服器在 name 使用 “role” 和 “ClaimTypes”,而不是使用 SOAP/WS-Fed 預設值。 當 MapInboundClaims 設定為 false時,處理常式不會執行宣告對應,而且應用程式會直接使用 JWT 的宣告名稱。 下列範例會將角色宣告類型設定為「roles」,這適用於 Microsoft Entra ID (ME-ID)。 如需詳細資訊,請參閱身分識別提供者的文件。

Note

針對多數 OIDC 提供者,MapInboundClaims 必須設定為 false,以防重新命名宣告。

oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters.NameClaimType = "name";
oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

路徑設定:路徑必須符合向 OIDC 提供者註冊應用程式時設定的重新導向 URI (登入回呼路徑),以及登出後重新導向 (登出回呼路徑)。 在 Azure 入口網站,路徑是在應用程式註冊的驗證面板設定。 登入和登出路徑都必須註冊為重新導向 URI。 預設值是 /signin-oidc/signout-callback-oidc

CallbackPath:應用程式基本路徑中用於返回使用者代理的請求路徑。

在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,{PORT} 佔位符是應用程式的埠:

https://localhost:{PORT}/signin-oidc

Note

使用 Microsoft Entra ID 時,localhost 位址不需要連接埠。 大部分的其他 OIDC 提供者都需要正確的埠。

SignedOutCallbackPath (組態鑰匙:"SignedOutCallbackPath"):OIDC 處理程式攔截的要求路徑位於應用程式的基底路徑中,當使用者代理從身份提供者註銷後,會首先被返回至此處理程式。 範例應用程式不會設定路徑的值,因為會使用預設值 「/signout-callback-oidc」。。 攔截要求之後,如果指定,OIDC 處理程式會重新導向至 SignedOutRedirectUriRedirectUri

在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,{PORT} 佔位符是應用程式的埠:

https://localhost:{PORT}/signout-callback-oidc

Note

使用 Microsoft Entra 識別符時,請在 Entra 或 Azure 入口網站中的 Web 平臺組態 重新導向 URI 項目中設定路徑。 使用 Entra 時,localhost 位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。 如果您未將已登出的回呼路徑 URI 新增至 Entra 中應用程式的註冊,Entra 將拒絕將使用者重新導回應用程式,只會要求他們關閉瀏覽器視窗。

RemoteSignOutPath:在這個路徑收到的要求會讓處理常式使用登出方案來叫用登出。

在下列範例中,{PORT} 佔位符是應用程式的埠:

https://localhost/signout-oidc

Note

使用 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;

針對 Microsoft Entra ID 或 Azure AD B2C,您可以使用來自 AddMicrosoftIdentityWebApp 的 Identity(Microsoft.Identity.Web),這將以適當的預設值新增 OIDC 和 認證處理程式。 本文中的範例應用程式和指引不會使用 Microsoft Identity Web。 這份指導示範如何為任何 OIDC 提供者手動設定 OIDC 處理常式。 如需實作 Microsoft Identity Web 的詳細資訊,請參閱使用 Microsoft Entra 標識符保護 ASP.NET CoreBlazor Web App

這個版本的文章介紹採用後端為前端 (BFF) 模式實作 OIDC。 如果應用程式規格不要求採用 BFF 模式,請將發行項版本選取器變更為 非 BFF 模式(互動式自動 轉譯)或 非 BFF 模式(互動式伺服器) (互動式伺服器)(互動式伺服器轉譯)。

Prerequisites

Aspire 需要 Visual Studio 17.10 版或更新版本。

此外,請參閱的Aspire一節。

範例解決方案

範例應用程式包含下列專案:

  • Aspire:
    • Aspire.AppHost:用來管理應用程式的高階協作事務。
    • Aspire.ServiceDefaults:包含預設的 Aspire 應用程式設定,該設定可視需要加以擴充及自訂。
  • MinimalApiJwt:後端 Web API,包含天氣資料的最小 API 端點範例。
  • BlazorWebAppOidc: Blazor Web App的伺服器端專案。 專案會使用 YARP,將要求通過 Proxy 處理至後端 Web API 專案 (MinimalApiJwt) 中的氣象預報端點,並將 access_token 儲存於驗證 cookie 中。
  • BlazorWebAppOidc.Client: Blazor Web App的用戶端專案。

使用下列連結,透過 Blazor 範例存放庫中的最新版本資料夾存取範例。 此範例位於 BlazorWebAppOidcBff .NET 8 或更新版本的資料夾中。

檢視或下載範例程式碼 \(英文\) (如何下載)

Blazor Web App 使用 自動轉譯模式搭配全域互動功能

伺服器專案會呼叫 AddAuthenticationStateSerialization 來新增伺服器端驗證狀態提供者,使用 PersistentComponentState 將驗證狀態傳送至用戶端。 用戶端會呼叫 AddAuthenticationStateDeserialization 來反序列化並使用伺服器所傳遞的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。

PersistingAuthenticationStateProvider 類別 (PersistingAuthenticationStateProvider.cs) 是伺服器端 AuthenticationStateProvider,使用 PersistentComponentState 將驗證狀態傳遞到用戶端,接著在 WebAssembly 應用程式的存留期內保持固定。

這個應用程式是任何 OIDC 驗證流程的起點。 OIDC 在應用程式手動設定,且不採用 Microsoft Entra IDMicrosoft Identity Web 套件,應用程式範例也不需要 Microsoft Azure 裝載。 不過,應用程式範例可與 Entra、Microsoft Identity Web 搭配使用,並且裝載於 Azure。

透過自訂 cookie 刷新器 (CookieOidcRefresher.cs) 協助的自動無需互動令牌刷新。

採用前端的後端 (BFF) 模式,使用 Aspire 進行服務探索,並使用 YARP 將請求代理到後端應用程式的天氣預報端點。

後端 Web API (MinimalApiJwt) 會使用 JWT 持有人驗證來驗證登入 Blazor Web App中所儲存cookie的 JWT 令牌。

Aspire 改善建置 .NET 雲端原生應用程式的體驗。 它提供一組一致固定的工具和模式,用於建置和執行分散式應用程式。

YARP (又一個反向 Proxy) 是用來建立反向 Proxy 伺服器的程式庫。 MapForwarderProgram 伺服器項目的 檔案中,會使用傳出要求的預設組態、自定義轉換和預設 HTTP 用戶端,將符合指定模式的 HTTP 要求直接轉送至特定目的地:

  • 在伺服器上轉譯 Weather 元件時,該元件使用 ServerWeatherForecaster 類別來代理天氣數據的請求,並附上使用者的存取令牌。 IHttpContextAccessor.HttpContext 確認 HttpContext 是否能被 GetWeatherForecastAsync 方法使用。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件
  • 在用戶端轉譯元件時,元件會使用 ClientWeatherForecaster 服務實作,它會使用預先設定的 HttpClient (位於用戶端專案的 Program 檔案) 對伺服器專案進行 Web API 呼叫。 伺服器專案檔案中的最小 API 端點 (/weather-forecast) 定義,將請求與使用者的存取權杖一起轉換,以取得天氣資料 (Program)。

如需在 Blazor Web App 中使用服務抽象概念呼叫 Web API 的詳細資訊,請參閱 從 ASP.NET Core Blazor 應用程式呼叫 Web API

Microsoft Entra ID 應用程式註冊

我們建議針對應用程式和 Web API 使用不同的註冊,即使應用程式和 Web API 位於相同的解決方案中也一樣。 下列指引適用於 BlazorWebAppOidc 範例解決方案的應用程式和 MinimalApiJwt Web API,但相同的指導方針通常適用於應用程式和 Web API 的任何以 Entra 為基礎的註冊。

先註冊 Web API (MinimalApiJwt),讓您可以在註冊應用程式時授與 Web API 的存取權。 Web API 的租用戶標識碼和用戶端識別碼可用來在其 Program 檔案中設定 Web API。 註冊 Web API 之後,請在應用程式註冊>中公開 WebAPI 公開具有 範圍名稱的 Weather.GetAPI。 記錄應用程式識別碼 URI 以用於應用程式的組態。

接下來,使用 BlazorWebAppOidc 平台組態,並將應用程式 /BlazorWebApOidc.Client 註冊到重定向 URIhttps://localhost/signin-oidc(不需要埠)。 應用程式的租使用者識別碼和用戶端標識碼,以及 Web API 的基地址、應用程式識別碼 URI 和天氣範圍名稱,可用來在其 Program 檔案中設定應用程式。 授與 API 許可權,以存取 應用程式註冊>API 許可權中的 Web API。 如果應用程式的安全性規格要求它,您可以授與系統管理員同意,讓組織存取 Web API。 授權的使用者和群組會指派給應用程式註冊>的應用程式註冊

在 Entra 或 Azure 入口網站的 隱含授與和混合式流程 應用程式註冊設定中,請勿選取授權端點的複選框以傳回 存取令牌標識碼令牌。 OpenID Connect 處理程式會使用從授權端點傳回的程式代碼,自動要求適當的令牌。

在 Entra 或 Azure 入口網站的應用程式註冊中建立客戶端密碼(管理>憑證和秘密>新客戶端密碼)。 請保留用戶端秘密 ,以使用下一節。

本文稍後會提供特定設定的其他 Entra 設定指引。

建立客戶端密碼

本節僅適用於 (Blazor Web App專案) 的伺服器BlazorWebAppOidc專案。

Warning

請勿在用戶端程式代碼中儲存應用程式秘密、連接字串、認證、密碼、個人標識碼 (PIN)、私人 C#/.NET 程式代碼或私鑰/令牌,這一律不安全。 在測試/預備和生產環境中,伺服器端 Blazor 程序代碼和 Web API 應該使用安全驗證流程,以避免在專案程式代碼或組態檔內維護認證。 在本機開發測試之外,建議您避免使用環境變數來儲存敏感數據,因為環境變數不是最安全的方法。 針對本機開發測試, 建議使用秘密管理員工具 來保護敏感數據。 如需詳細資訊,請參閱 安全地維護敏感數據和認證

若要進行本機開發測試,請使用 秘密管理員工具 ,將伺服器專案的用戶端密碼儲存 Blazor 在組態密鑰 Authentication:Schemes:MicrosoftOidc:ClientSecret之下。

伺服器專案尚未為秘密管理員工具初始化。 使用命令殼層,例如 Visual Studio 中的開發人員 PowerShell 命令殼層,執行下列命令。 在執行命令之前,請將 目錄與 cd 命令變更為伺服器項目的目錄。 命令會建立使用者密碼識別碼(<UserSecretsId> 在伺服器應用程式的項目檔中):

dotnet user-secrets init

執行下列命令來設定客戶端密碼。 占位符 {SECRET} 是從應用程式註冊中獲取的用戶端密鑰。

dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"

如果使用 Visual Studio,您可以用滑鼠右鍵按兩下 方案總管 中的伺服器專案,然後選取 [管理用戶密碼],以確認秘密已設定。

Aspire 專案

如需有關使用 Aspire 的詳細資訊,以及應用程式範例 .AppHost.ServiceDefaults 專案的詳細資料,請參閱 Aspire 文件

確認您已符合 Aspire 的必要條件。 如需詳細資訊,請參閱的Aspire一節。

範例應用程式只會設定不安全的 HTTP 啟動設定檔 (http),以在開發測試期間使用。 如需詳細資訊,包括不安全且安全的啟動設定設定檔範例,請參閱允許 Aspire 中不安全的傳輸(Aspire 文件)

MinimalApiJwt 專案

MinimalApiJwt 專案是多個前端專案的後端 Web API。 專案會為天氣資料設定最小 API 端點。 來自 Blazor Web App 伺服器端專案 (BlazorWebAppOidc) 的要求會通過 Proxy 處理至 MinimalApiJwt 專案。

MinimalApiJwt.http 檔案可用於測試天氣資料要求。 請注意,MinimalApiJwt 專案必須執行才能測試端點,而且端點會硬式編碼到檔案中。 如需詳細資訊,請參閱在 Visual Studio 2022 中使用 .http 檔案

專案包含用來產生 OpenAPI 檔的套件和配置。

專案包含套件和元件,可在開發環境中產生OpenAPI檔和Swagger UI。 如需詳細資訊,請參閱 使用產生的OpenAPI檔

安全的氣象預報數據端點位於專案的 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] 屬性 新增至控制器或動作。

在專案的 JwtBearerOptions 檔案中,於 AddJwtBearer 呼叫中的 Program 進行專案設定。

Authority 設定 OIDC 呼叫的 Authority。 我們建議針對 MinimalApiJwt 專案使用不同的應用程式註冊。 授權機構與身分提供者所傳回的 JWT 的發行者(iss)相匹配。

jwtOptions.Authority = "{AUTHORITY}";

授權格式取決於使用中的租戶類型。 下列 Microsoft Entra ID 的範例會使用租用戶 ID aaaabbbb-0000-cccc-1111-dddd2222eeee

ME-ID 租戶權限範例:

jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee";

AAD B2C 租戶授權單位範例:

jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";

Audience 用於指定任何收到的 OIDC 令牌的受眾。

jwtOptions.Audience = "{APP ID URI}";

Note

使用 Microsoft Entra ID 時,請將值與在 Entra 或 Azure 入口網站中 [公開 API] 底下新增Weather.Get範圍時所設定的應用程式識別碼 URI 路徑相符。 請勿在值中包含範圍名稱 "Weather.Get"。

受眾的格式取決於正在使用的租用戶類型。 下列 Microsoft Entra ID 的範例中使用了租用戶識別碼 contoso 和用戶端識別碼 11112222-bbbb-3333-cccc-4444dddd5555

ME-ID 租戶應用程式識別碼 URI 範例:

jwtOptions.Audience = "api://11112222-bbbb-3333-cccc-4444dddd5555";

AAD B2C 租使用者應用程式識別碼 URI 範例:

jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555";

伺服器端 Blazor Web App 專案 (BlazorWebAppOidc)

本節說明如何設定伺服器端 Blazor 專案。

以下 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;

openidprofile (Scope) 的範圍 (選擇性):預設也會設定 openidprofile 範圍,因為 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):設定 Weather.Get 存取天氣數據外部 Web API 的範圍。 在下列範例中, {APP ID URI} 佔位符位於公開 Web API 的 Entra 或 Azure 入口網站中。 針對任何其他識別提供者,請使用適當的範圍。

oidcOptions.Scope.Add("{APP ID URI}/Weather.Get");

使用範圍的格式取決於租用戶的型態。 在下列範例中,租使用者網域為 contoso.onmicrosoft.com,而用戶端識別碼為 11112222-bbbb-3333-cccc-4444dddd5555

ME-ID 租戶應用程式識別碼 URI 範例:

oidcOptions.Scope.Add("api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get");

AAD B2C 租使用者應用程式識別碼 URI 範例:

oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get");

AuthorityClientId:設定 OIDC 呼叫的授權單位和用戶端識別碼。

oidcOptions.Authority = "{AUTHORITY}";
oidcOptions.ClientId = "{CLIENT ID}";

下列範例使用 的 aaaabbbb-0000-cccc-1111-dddd2222eeee 租使用者識別碼和 的 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";

針對多租戶應用程式,應該使用「common」授權。 您也可以針對單一租用戶應用程式使用「通用」授權方式,但必須使用自訂的 IssuerValidator,如本節稍後所示。

oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0";

ResponseType:將 OIDC 處理常式設定為只執行授權碼流程。 在這個模式中,不需要隱含授與和混合式流程。 OIDC 處理程序使用從授權端點傳回的授權碼,自動要求適當的權杖。

oidcOptions.ResponseType = OpenIdConnectResponseType.Code;

MapInboundClaimsNameClaimTypeRoleClaimType 的設定:許多 OIDC 伺服器在 name 使用 “role” 和 “ClaimTypes”,而不是使用 SOAP/WS-Fed 預設值。 MapInboundClaims 設定為 false 時,處理常式不會執行宣告對應,而且應用程式會直接使用 JWT 的宣告名稱。 下列範例會將角色宣告類型設定為「roles」,這適用於 Microsoft Entra ID (ME-ID)。 如需詳細資訊,請參閱身分識別提供者的文件。

Note

針對多數 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

Note

使用 Microsoft Entra ID 時,localhost 位址不需要連接埠。 大部分的其他 OIDC 提供者都需要正確的埠。

SignedOutCallbackPath (組態鑰匙:"SignedOutCallbackPath"):OIDC 處理程式攔截的要求路徑位於應用程式的基底路徑中,當使用者代理從身份提供者註銷後,會首先被返回至此處理程式。 範例應用程式不會設定路徑的值,因為會使用預設值 「/signout-callback-oidc」。。 攔截要求之後,如果指定,OIDC 處理程式會重新導向至 SignedOutRedirectUriRedirectUri

在應用程式的 OIDC 提供者註冊中設定登出後的回呼路徑。 在下列範例中,{PORT} 佔位符是應用程式的埠:

https://localhost:{PORT}/signout-callback-oidc

Note

使用 Microsoft Entra 識別符時,請在 Entra 或 Azure 入口網站中的 Web 平臺組態 重新導向 URI 項目中設定路徑。 使用 Entra 時,localhost 位址不需要埠。 大部分的其他 OIDC 提供者都需要正確的埠。 如果您未將已登出的回呼路徑 URI 新增至 Entra 中應用程式的註冊,Entra 將拒絕將使用者重新導回應用程式,只會要求他們關閉瀏覽器視窗。

RemoteSignOutPath:在這個路徑收到的要求會讓處理常式使用登出方案來叫用登出。

在下列範例中,{PORT} 佔位符是應用程式的埠:

https://localhost/signout-oidc

Note

使用 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;

Blazor Web App 用戶端專案 (BlazorWebAppOidc.Client

BlazorWebAppOidc.Client 專案是 Blazor Web App的用戶端專案。

用戶端會呼叫 AddAuthenticationStateDeserialization 來反序列化並使用伺服器所傳遞的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。

PersistentAuthenticationStateProvider 類別 (PersistentAuthenticationStateProvider.cs) 是一個客戶端 AuthenticationStateProvider,藉由尋找在伺服器端渲染時保存在頁面中的資料,來判斷使用者的驗證狀態。 驗證狀態在 WebAssembly 應用程式的整個生命周期中是固定的。

如果使用者需要登入或登出,則需要完整頁面重新載入。

應用程式範例提供使用者名稱和電子郵件僅供顯示之用。

僅將名稱和角色權利要求序列化

本節僅適用於非 BFF 模式 (Interactive Auto) 和 BFF 模式 (Interactive Auto) 及其範例應用程式。

Program 檔案中,所有聲明都會藉由將SerializeAllClaims設定為true來進行串行化。 如果您只想針對 CSR 序列化名稱與角色聲明,請移除選項,或將其設定為 false

使用 JSON 設定提供者提供設定(應用程式設定)

範例解決方案專案會在其Program檔案中設定 OIDC 和 JWT 持有人驗證,以便使用 C# 自動完成來探索組態設定。 專業應用程式通常會使用 設定提供者 來設定 OIDC 選項,例如預設 的 JSON 設定提供者。 JSON 組態提供者會從應用程式配置檔 appsettings.json/appsettings.{ENVIRONMENT}.json載入組態,其中 {ENVIRONMENT} 佔位元是應用程式的 運行時間環境。 請遵循本節中的指引,使用應用程式配置檔進行組態。

在或 appsettings.json 項目的應用程式設定檔中BlazorWebAppOidcBlazorWebAppOidcServer,新增下列 JSON 組態:

"Authentication": {
  "Schemes": {
    "MicrosoftOidc": {
      "Authority": "https://login.microsoftonline.com/{TENANT ID (BLAZOR APP)}/v2.0",
      "ClientId": "{CLIENT ID (BLAZOR APP)}",
      "CallbackPath": "/signin-oidc",
      "SignedOutCallbackPath": "/signout-callback-oidc",
      "RemoteSignOutPath": "/signout-oidc",
      "SignedOutRedirectUri": "/",
      "Scope": [
        "openid",
        "profile",
        "offline_access",
        "{APP ID URI (WEB API)}/Weather.Get"
      ]
    }
  }
},

更新上述組態中的佔位元,以符合應用程式在檔案中使用的 Program 值:

  • {TENANT ID (BLAZOR APP)}:Blazor 應用程式的租用戶 ID。
  • {CLIENT ID (BLAZOR APP)}:應用程式的用戶端識別碼 Blazor 。
  • {APP ID URI (WEB API)}:Web API 的應用程式識別碼 URI。

「通用」授權單位 (https://login.microsoftonline.com/common/v2.0) 應該用於多租用戶應用程式。 若要針對單一租使用者應用程式使用「通用」授權單位,請參閱 使用單一租用戶應用程式的「通用」授權單位 一節。

更新上述組態中的任何其他值,以符合檔案中使用的 Program 自定義/非預設值。

驗證產生器會自動挑選組態。

Program 檔案中移除下列幾行:

- oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);
- oidcOptions.Scope.Add("...");
- oidcOptions.CallbackPath = new PathString("...");
- oidcOptions.SignedOutCallbackPath = new PathString("...");
- oidcOptions.RemoteSignOutPath = new PathString("...");
- oidcOptions.Authority = "...";
- oidcOptions.ClientId = "...";

ConfigureCookieOidc的方法中CookieOidcServiceCollectionExtensions.cs,移除下列這一行:

- oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);

MinimalApiJwt 專案中,將下列應用程式設定組態新增至 appsettings.json 檔案:

"Authentication": {
  "Schemes": {
    "Bearer": {
      "Authority": "https://sts.windows.net/{TENANT ID (WEB API)}",
      "ValidAudiences": [ "{APP ID URI (WEB API)}" ]
    }
  }
},

更新上述組態中的佔位元,以符合應用程式在檔案中使用的 Program 值:

  • {TENANT ID (WEB API)}:Web API 的租用戶標識符。
  • {APP ID URI (WEB API)}:Web API 的應用程式識別碼 URI。

權威格式採用下列模式:

  • ME-ID 承租者類型: https://sts.windows.net/{TENANT ID}
  • Microsoft Entra 外部識別碼: https://{DIRECTORY NAME}.ciamlogin.com/{TENANT ID}/v2.0
  • B2C 租戶類型: https://login.microsoftonline.com/{TENANT ID}/v2.0

物件格式採用下列模式({CLIENT ID}是 Web API 的用戶端識別元,{DIRECTORY NAME}例如目錄名稱): contoso

  • ME-ID 承租者類型: api://{CLIENT ID}
  • Microsoft Entra 外部識別碼: {CLIENT ID}
  • B2C 租戶類型: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}

JWT 持有人驗證產生器會自動挑選組態。

Program 檔案中移除下列幾行:

- jwtOptions.Authority = "...";
- jwtOptions.Audience = "...";

如需設定的詳細資訊,請參閱下列資源:

針對單一租用戶應用程式使用「一般」授權

您可以使用單一租使用者應用程式的「通用」授權單位,但您必須採取下列步驟來實作自定義簽發者驗證程式。

Microsoft.IdentityModel.Validators NuGet 套件 新增至 BlazorWebAppOidcBlazorWebAppOidcServerBlazorWebAppOidcBff 專案。

Note

如需將套件新增至 .NET 應用程式的指引,請參閱 套件取用工作流程 (NuGet 文件) 下的安裝和管理套件相關文章。 在 NuGet.org確認正確的套件版本。

Program 檔案的頂端,啟用 Microsoft.IdentityModel.Validators 命名空間以供使用:

using Microsoft.IdentityModel.Validators;

在設定 OIDC 選項的檔案中,使用下列程式代碼:

var microsoftIssuerValidator = 
    AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
oidcOptions.TokenValidationParameters.IssuerValidator = 
    microsoftIssuerValidator.Validate;

如需詳細資訊,請參閱 關於 OpenID Connect 和 Azure AD「通用」端點的 SecurityTokenInvalidIssuerException (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet #1731)

登出後重新導向至首頁

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 令牌,以換取重新整理令牌。 接著會使用此 ID 令牌中的宣告來覆寫使用者的宣告。

範例實作不包含針對重新整理令牌時,從UserInfo 端點請求宣告的程式代碼。 如需詳細資訊,請參閱 BlazorWebAppOidc AddOpenIdConnect with GetClaimsFromUserInfoEndpoint = true doesn't propogate [sic] role claims to clientdotnet/aspnetcore #58826)

Note

有些身份提供者 只有在使用刷新令牌時才會返回存取令牌。 CookieOidcRefresher 可以用額外的邏輯來更新,以持續使用儲存在驗證 cookie 中的過去宣告集合,或使用存取令牌向 UserInfo 端點請求宣告。

加密隨機數

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.RoleClaimTypeOpenIdConnectOptions 中設定角色宣告類型 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.RoleClaimTypeOpenIdConnectOptions 中設定角色宣告類型 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 屬性:

  1. 請在 Azure 入口網站開啟應用程式的註冊。
  2. 在側邊欄中選取管理>資訊清單
  3. 尋找 groupMembershipClaims 屬性。
  4. 將值設定為 All ("groupMembershipClaims": "All")。
  5. 選取 [儲存] 按鈕。

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 傳送至租用戶的非訪客帳戶,且不涉及系統管理員角色。 在 Azure 入口網站中,當您選取 角色與系統管理員後,系統管理員角色 GUID(角色範本 ID)會出現,接著是省略符號(...)>所列角色的描述。 角色範本 ID 也會列在 Microsoft Entra 內建角色中(Entra 文件)

替代方案:Duende 存取令牌管理

在範例應用程式中,會使用自訂 cookie 重新整理器 (CookieOidcRefresher.cs) 實作來執行自動非互動式權杖重新整理。 可以在開源 Duende.AccessTokenManagement.OpenIdConnect中找到替代解決方案。

Duende 存取權杖管理為 .NET Worker 和 ASP.NET Core Web 應用程式提供自動存取權杖管理功能,包括 Blazor,無需新增自訂 cookie 重新整理器。

安裝套件之後,請移除檔案中CookieOidcRefresher目前登入使用者的存取Program權杖管理,並新增:

// Add services for token management
builder.Services.AddOpenIdConnectAccessTokenManagement();

// Register a typed HTTP client with token management support
builder.Services.AddHttpClient<InvoiceClient>(client =>
    {
        client.BaseAddress = new Uri("https://api.example.com/invoices/");
    })
    .AddUserAccessTokenHandler();

類型化的 HTTP 用戶端 (或具名 HTTP 用戶端,如果實作的話) 具有代表目前登入使用者的自動存取權杖存留期管理,包括透明重新整理權杖管理。

如需詳細資訊,請參閱 的 Duende 存取權杖管理文件 Blazor

Troubleshoot

Logging

伺服器應用程式是標準 ASP.NET Core 應用程式。 請參閱 ASP.NET Core 記錄指導,在伺服器應用程式啟用較低的記錄層級。

若要啟用Blazor WebAssembly驗證的除錯或追蹤記錄,請參閱中的Blazor一節,並確保文章版本選擇器設為 .NET 7 或更新版本中的 ASP.NET Core。

常見錯誤

  • 偵錯工具在登出時因例外情況暫停,使用 Microsoft Entra 外部標識碼。

    下列例外狀況會在註銷期間停止 Visual Studio 調試程式,Microsoft Entra 外部標識符

    Uncaught TypeError TypeError: Failed to execute 'postMessage' on 'Window': The provided value cannot be converted to a sequence.

    Visual Studio 調試程式在註銷期間因 JavaScript 例外狀況而中斷

    例外狀況是從 Entra JavaScript 程式碼拋出,因此這不是 ASP.NET Core 的問題。 例外狀況不會影響生產環境中的應用程式功能,因此可以在本機開發測試期間忽略例外狀況。

  • 應用程式或 Identity 提供者 (IP) 的設定錯誤

    最常見的錯誤是由不正確的設定所造成。 以下是一些範例:

    • 視案例的需求而定,遺漏或不正確的授權單位、執行個體、租用戶識別碼、租用戶網域、用戶端識別碼或重新導向 URI 會防止應用程式驗證用戶端。
    • 不正確的要求範圍會防止用戶端存取伺服器 Web API 端點。
    • 不正確或遺漏伺服器 API 權限會防止用戶端存取伺服器 Web API 端點。
    • 在不同於 IP 應用程式註冊中重定向 URI 所設定的連接埠上運行應用程式。 請注意,Microsoft Entra ID 和在 localhost 開發測試位址執行的應用程式不需要連接埠,但應用程式的連接埠設定和執行應用程式的連接埠必須與非 localhost 位址相符。

    本文中的設定範圍顯示正確配置的範例。 請仔細查看設定,確定應用程式和 IP 是否設定錯誤。

    如果設定顯示正確:

    • 分析應用程式記錄檔。

    • 使用瀏覽器的開發人員工具,檢查用戶端應用程式與 IP 或伺服器應用程式之間的網路流量。 通常,在提出要求之後,IP 或伺服器應用程式會傳回錯誤訊息或有導致問題的線索訊息給用戶端。 下列文章中可找到開發人員工具指導:

    文件小組會回應文章中的文件回饋和錯誤(從此頁面意見反應區段開啟問題),但是無法提供產品支援服務。 有數個公用支援論壇可用來協助針對應用程式進行疑難排解。 我們建議下列事項:

    上述論壇並非由 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.

    若要解決此錯誤:

    1. 在 Azure 入口網站中,存取應用程式的資訊清單
    2. allowPublicClient 屬性設定為 nulltrue

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
    • 在 [引數] 欄位中,提供瀏覽器用來在 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)。
    • 在 [友好名稱] 欄位中輸入名稱。 例如: Firefox Auth Testing
    • 選取確定按鈕。
    • 若要避免針對使用應用程式測試的每個反覆項目選取瀏覽器設定檔,請使用 [設為預設值] 按鈕,將設定檔設定為預設值。
    • 請確定瀏覽器已由 IDE 關閉,以便對應用程式、測試使用者或提供者設定進行任何變更。

應用程式升級

在升級開發電腦上的 .NET SDK 或變更應用程式內的套件版本之後,運作中的應用程式可能會立即失敗。 在某些情況下,執行主要升級時,不一致的套件可能會中斷應用程式。 大多數這些問題都可依照下列指示來進行修正:

  1. 從命令提示字元執行 dotnet nuget locals all --clear 以清除本機系統的 NuGet 套件快取。
  2. 刪除專案的 binobj 資料夾。
  3. 還原並重建專案。
  4. 在重新部署應用程式之前,請先刪除伺服器上部署資料夾中的所有檔案。

Note

不支援使用與應用程式目標框架不相容的套件版本。 如需套件的相關資訊,請使用 NuGet Gallery

從正確的項目啟動方案

Blazor Web App:

  • 針對其中一個 Backend-for-Frontend (BFF) 模式範例,請從 Aspire/Aspire.AppHost 專案啟動解決方案。
  • 針對其中一個非 BFF 模式範例,請從 伺服器項目啟動方案。

Blazor Server:

伺服器專案啟動解決方案。

檢查使用者

下列 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;
    }
}

其他資源