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

注意

此版本不是本文的最新版本。 有关当前版本,请参阅本文.NET 9 版本。

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

有关当前版本,请参阅本文.NET 9 版本。

本文介绍如何使用 dotnet/blazor-samples GitHub 存储库(.NET 8 或更高版本)中的示例应用通过 OpenID Connect (OIDC) 来保护 Blazor Web App(如何下载)。

本文的这个版本介绍了如何在不采用服务于前端的后端 (BFF) 模式的情况下实现 OIDC。 BFF 模式用于向外部服务发出经验证的请求。 如果应用的规范要求采用 BFF 模式,请将文章版本选择器更改为“采用 BFF 模式的 OIDC”。

涵盖以下规范:

  • Blazor Web App 使用具有全局交互性的自动呈现模式
  • 服务器和客户端应用使用自定义身份验证状态提供程序服务来捕获用户的身份验证状态并在服务器和客户端之间传递它。
  • 此应用是任何 OIDC 身份验证流的起点。 OIDC 在应用中手动配置,不依赖于 Microsoft Entra IDMicrosoft Identity Web 包,示例应用也不需要 Microsoft Azure 托管。 不过,示例应用可以与 Entra、Microsoft Identity Web 一起使用,并托管在 Azure 中。
  • 自动非交互式令牌刷新。
  • 安全地调用服务器项目中的 (Web) API 以获取数据。

示例应用

此示例应用包含两个项目:

  • BlazorWebAppOidc:Blazor Web App 的服务器端项目,包含用于天气数据的示例最小 API 终结点。
  • BlazorWebAppOidc.Client:Blazor Web App 的客户端项目。

使用以下链接通过存储库根目录中的最新版本文件夹访问示例应用。 这些项目位于 .NET 8 或更高版本的 BlazorWebAppOidc 文件夹中。

查看或下载示例代码如何下载

服务器端 Blazor Web App 项目 (BlazorWebAppOidc)

BlazorWebAppOidc 项目是 Blazor Web App 的服务器端项目。

BlazorWebAppOidc.http 文件可用于测试天气数据请求。 请注意,BlazorWebAppOidc 项目必须正在运行以测试终结点,并且终结点已硬编码到文件中。 有关详细信息,请参阅在 Visual Studio 2022 中使用 .http 文件

注意

服务器项目使用 IHttpContextAccessor/HttpContext,但始终不会用于交互式呈现的组件。 有关详细信息,请参阅 ASP.NET Core Blazor 交互式服务器端呈现的威胁缓解指南

配置

本部分介绍如何配置示例应用。

注意

对于 Microsoft Entra ID 或 Azure AD B2C,可以使用 AddMicrosoftIdentityWebApp Microsoft Identity WebMicrosoft.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 门户(管理>证书和机密>新客户端机密)中的应用注册中创建客户端密码。 在以下指南中使用新机密的值

尚未为机密管理器工具初始化示例应用。 使用命令 shell(如 Visual Studio 中的开发人员 PowerShell 命令 shell)执行以下命令。 在执行命令之前,请将包含 cd 该命令的目录更改为服务器项目的目录。 该命令将建立用户机密标识符(<UserSecretsId> 在服务器应用的项目文件中):

dotnet user-secrets init

执行以下命令以设置客户端密码。 占 {SECRET} 位符是从应用的注册中获取的客户端密码:

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

如果使用 Visual Studio,可以通过右键单击解决方案资源管理器中的服务器项目并选择“管理用户机密”来确认是否已设置机密

配置应用

调用 AddOpenIdConnect 时,可以在项目的 Program 文件中找到以下 OpenIdConnectOptions 配置:

  • SignInScheme:设置与身份验证成功后负责保留用户 identity 的中间件对应的身份验证方案。 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 中存储访问令牌和刷新令牌。 此属性设置为 false,以减小最终身份验证 cookie 的大小。

    oidcOptions.SaveTokens = false;
    
  • 脱机访问范围 (Scope):刷新令牌需要 offline_access 范围。

    oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);
    
  • AuthorityClientId:设置 OIDC 调用的颁发机构和客户端 ID。

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

    示例:

    • 颁发机构 ({AUTHORITY}):https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/(使用租户 ID aaaabbbb-0000-cccc-1111-dddd2222eeee
    • 客户端 ID ({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“通用”颁发机构示例:

    “通用”颁发机构应该用于多租户应用。 还可以对单租户应用使用“通用”颁发机构,但需要自定义 IssuerValidator,如本部分后面所示。

    oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0/";
    
  • ResponseType:将 OIDC 处理程序配置为仅执行授权代码流。 在这种模式下,隐式授权和混合流是不必要的。

    在 Entra 或 Azure 门户的“隐式授权和混合流”应用注册配置中,不要选中授权终结点的复选框以返回“访问令牌”或“ID 令牌”。 OIDC 处理程序使用从授权终结点返回的代码自动请求适当的令牌。

    oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
    
  • MapInboundClaims 以及 NameClaimTypeRoleClaimType 的配置:许多 OIDC 服务器使用“name”和“role”,而不使用 ClaimTypes 中的 SOAP/WS-Fed 默认值。 当 MapInboundClaims 设置为 false 时,处理程序不执行声明映射,应用直接使用 JWT 中的声明名称。 以下示例将角色声明类型设置为“roles”,该类型适用于 Microsoft Entra ID (ME-ID)。 参阅 identity 提供程序文档了解详细信息。

    注意

    MapInboundClaims 必须为大多数 OIDC 提供程序设置为 false,这会阻止重命名声明。

    oidcOptions.MapInboundClaims = false;
    oidcOptions.TokenValidationParameters.NameClaimType = "name";
    oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
    
  • 路径配置:路径必须与向 OIDC 提供程序注册应用程序时配置的重定向 URI 路径(登录回叫路径)和退出登录后重定向路径(退出登录回叫路径)匹配。 在 Microsoft Azure 门户中,路径是在应用注册的“身份验证”边栏选项卡中配置的。 登录路径和退出登录路径都必须注册为重定向 URI。 默认值为 /signin-oidc/signout-callback-oidc

    • CallbackPath:应用的基路径内的请求路径,将在其中返回用户代理。

      在 Entra 或 Microsoft Azure 门户中,设置 Web 平台配置的“重定向 URI”中的路径:

      https://localhost/signin-oidc

      注意

      使用 Microsoft Entra ID 时,localhost 地址不需要端口。 其他的大多数 OIDC 提供程序都需要正确的端口。

    • SignedOutCallbackPath:应用基本路径内的请求路径,从 identity 提供程序退出登录后返回用户代理。

      在 Entra 或 Microsoft Azure 门户中,设置 Web 平台配置的“重定向 URI”中的路径:

      https://localhost/signout-callback-oidc

      注意

      使用 Microsoft Entra ID 时,localhost 地址不需要端口。 其他的大多数 OIDC 提供程序都需要正确的端口。

      注意

      如果使用 Microsoft Identity Web,则提供者当前仅在使用 microsoftonline.com 颁发机构 (https://login.microsoftonline.com/{TENANT ID}/v2.0/) 时重定向回 SignedOutCallbackPath。 如果你可以使用 Microsoft Identity Web 的“通用”颁发机构,则不存在此限制。 有关详细信息,请参阅当颁发机构 URL 包含租户 ID 时 postLogoutRedirectUri 不起作用 (AzureAD/microsoft-authentication-library-for-js #5783)

    • RemoteSignOutPath:在此路径上收到的请求会导致处理程序使用退出登录方案调用退出登录。

      在 Entra 或 Microsoft Azure 门户中,设置“前端通道退出登录 URL”:

      https://localhost/signout-oidc

      注意

      使用 Microsoft Entra ID 时,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 提供程序都使用默认颁发者验证器,但我们需要考虑使用由 https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration 返回的租户 ID ({TENANT ID}) 参数化的颁发者。 有关详细信息,请参阅 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 请求天气数据的示例请求由 Program 文件 (Program.cs) 中的最小 API 终结点 (/weather-forecast) 处理。 终结点要求通过调用 RequireAuthorization 进行授权。 对于添加到项目中的任何控制器,请将 [Authorize] 属性添加到控制器或操作中。
  • 该应用安全地调用服务器项目中的 (Web) API 来获取天气数据:
    • 在服务器上呈现 Weather 组件时,该组件使用服务器上的 ServerWeatherForecaster 直接获取天气数据(而不是通过 Web API 调用)。
    • 在客户端上呈现该组件时,该组件使用 ClientWeatherForecaster 服务实现,而该实现使用预配置的 HttpClient(在客户端项目的 Program 文件中)对服务器项目进行 Web API 调用。 服务器项目的 Program 文件中定义的最小 API 终结点 (/weather-forecast) 从 ServerWeatherForecaster 获取天气数据,并将数据返回到客户端。
  • 借助自定义 cookie 刷新器 (CookieOidcRefresher.cs) 进行的自动非交互式令牌刷新。
  • PersistingAuthenticationStateProvider 类 (PersistingAuthenticationStateProvider.cs) 是服务器端 AuthenticationStateProvider,它使用 PersistentComponentState 将身份验证状态传输到客户端,然后该状态在 WebAssembly 应用程序的生存期内得到修复。
  • 向 Blazor Web App 请求天气数据的示例请求由 Program 文件 (Program.cs) 中的最小 API 终结点 (/weather-forecast) 处理。 终结点要求通过调用 RequireAuthorization 进行授权。 对于添加到项目中的任何控制器,请将 [Authorize] 属性添加到控制器或操作中。
  • 该应用安全地调用服务器项目中的 (Web) API 来获取天气数据:
    • 在服务器上呈现 Weather 组件时,该组件使用服务器上的 ServerWeatherForecaster 直接获取天气数据(而不是通过 Web API 调用)。
    • 在客户端上呈现该组件时,该组件使用 ClientWeatherForecaster 服务实现,而该实现使用预配置的 HttpClient(在客户端项目的 Program 文件中)对服务器项目进行 Web API 调用。 服务器项目的 Program 文件中定义的最小 API 终结点 (/weather-forecast) 从 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 请求中)。

本文的这个版本介绍了如何在使用服务于前端的后端 (BFF) 模式的情况下实现 OIDC。 如果应用的规范没有要求采用 BFF 模式,请将文章版本选择器更改为“没有采用 BFF 模式的 OIDC”。

涵盖以下规范:

  • Blazor Web App 使用具有全局交互性的自动呈现模式
  • 服务器和客户端应用使用自定义身份验证状态提供程序服务来捕获用户的身份验证状态并在服务器和客户端之间传递它。
  • 此应用是任何 OIDC 身份验证流的起点。 OIDC 在应用中手动配置,不依赖于 Microsoft Entra IDMicrosoft Identity Web 包,示例应用也不需要 Microsoft Azure 托管。 不过,示例应用可以与 Entra、Microsoft Identity Web 一起使用,并托管在 Azure 中。
  • 自动非交互式令牌刷新。
  • 采用服务于前端的后端 (BFF) 模式,使用 .NET Aspire 进行服务发现,使用 YARP 将请求代理到后端应用上的天气预报终结点。
    • 后端 Web API 使用 JWT 持有者身份验证来验证登录 cookie 中的 Blazor Web App 保存的 JWT 令牌。
    • Aspire 改善了生成 .NET 云原生应用的体验。 它提供了一套一致的、固定的工具和模式,用于生成和运行分布式应用。
    • YARP (Yet Another Reverse Proxy) 是一个用于创建反向代理服务器的库。

有关 .NET Aspire 的详细信息,请参阅 .NET Aspire 正式发布:简化 .NET 云原生开发(2024 年 5 月)

先决条件

.NET Aspire 需要 17.10 版或更高版本的 Visual Studio

示例应用

此示例应用包含五个项目:

  • .NET Aspire:
    • Aspire.AppHost:用于管理应用的高级编排问题。
    • Aspire.ServiceDefaults:包含可根据需要进行扩展和自定义的默认 .NET Aspire 应用配置。
  • MinimalApiJwt:后端 Web API,包含天气数据的示例最小 API 终结点。
  • BlazorWebAppOidc:Blazor Web App 的服务器端项目。
  • BlazorWebAppOidc.Client:Blazor Web App 的客户端项目。

使用以下链接通过存储库根目录中的最新版本文件夹访问示例应用。 这些项目位于 .NET 8 或更高版本的 BlazorWebAppOidcBff 文件夹中。

查看或下载示例代码如何下载

.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 来代理对后端 Web API 项目 (MinimalApiJwt) 中的天气预报终结点发出的请求,并将 access_token 存储在身份验证 cookie 中。

BlazorWebAppOidc.http 文件可用于测试天气数据请求。 请注意,BlazorWebAppOidc 项目必须正在运行以测试终结点,并且终结点已硬编码到文件中。 有关详细信息,请参阅在 Visual Studio 2022 中使用 .http 文件

注意

服务器项目使用 IHttpContextAccessor/HttpContext,但始终不会用于交互式呈现的组件。 有关详细信息,请参阅 ASP.NET Core Blazor 交互式服务器端呈现的威胁缓解指南

配置

本部分介绍如何配置示例应用。

注意

对于 Microsoft Entra ID 或 Azure AD B2C,可以使用 AddMicrosoftIdentityWebApp Microsoft Identity WebMicrosoft.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 门户(管理>证书和机密>新客户端机密)中的应用注册中创建客户端密码。 在以下指南中使用新机密的值

尚未为机密管理器工具初始化示例应用。 使用命令 shell(如 Visual Studio 中的开发人员 PowerShell 命令 shell)执行以下命令。 在执行命令之前,请将包含 cd 该命令的目录更改为服务器项目的目录。 该命令将建立用户机密标识符(<UserSecretsId> 在服务器应用的项目文件中):

dotnet user-secrets init

执行以下命令以设置客户端密码。 占 {SECRET} 位符是从应用的注册中获取的客户端密码:

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

如果使用 Visual Studio,可以通过右键单击解决方案资源管理器中的服务器项目并选择“管理用户机密”来确认是否已设置机密

配置应用

调用 AddOpenIdConnect 时,可以在项目的 Program 文件中找到以下 OpenIdConnectOptions 配置:

  • SignInScheme:设置与身份验证成功后负责保留用户 identity 的中间件对应的身份验证方案。 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 范围是在 Azure 或 Entra 门户中的“公开 API”下配置的。 这对于后端 Web API 项目 (MinimalApiJwt) 使用持有者 JWT 来验证访问令牌是必要的。

    oidcOptions.Scope.Add("{APP ID URI}/{API NAME}");
    

    示例:

    • App ID URI ({APP ID URI}):https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}
      • 目录名称 ({DIRECTORY NAME}):contoso
      • 应用程序(客户端)ID ({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 租户中注册,则应用 ID URI 不同,因此范围不同。

    示例:

    • 应用 ID URI ({APP ID URI}):api://{CLIENT ID},应用程序(客户端)ID ({CLIENT ID}):00001111-aaaa-2222-bbbb-3333cccc4444
    • 为来自 MinimalApiJwt ({API NAME}) 的天气数据配置的范围:Weather.Get
    oidcOptions.Scope.Add("api://00001111-aaaa-2222-bbbb-3333cccc4444/Weather.Get");
    
  • AuthorityClientId:设置 OIDC 调用的颁发机构和客户端 ID。

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

    示例:

    • 颁发机构 ({AUTHORITY}):https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/(使用租户 ID aaaabbbb-0000-cccc-1111-dddd2222eeee
    • 客户端 ID ({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“通用”颁发机构示例:

    “通用”颁发机构应该用于多租户应用。 还可以对单租户应用使用“通用”颁发机构,但需要自定义 IssuerValidator,如本部分后面所示。

    oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0/";
    
  • ResponseType:将 OIDC 处理程序配置为仅执行授权代码流。 在这种模式下,隐式授权和混合流是不必要的。

    在 Entra 或 Azure 门户的“隐式授权和混合流”应用注册配置中,不要选中授权终结点的复选框以返回“访问令牌”或“ID 令牌”。 OIDC 处理程序使用从授权终结点返回的代码自动请求适当的令牌。

    oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
    
  • MapInboundClaims 以及 NameClaimTypeRoleClaimType 的配置:许多 OIDC 服务器使用“name”和“role”,而不使用 ClaimTypes 中的 SOAP/WS-Fed 默认值。 当 MapInboundClaims 设置为 false 时,处理程序不执行声明映射,应用直接使用 JWT 中的声明名称。 以下示例将角色声明类型设置为“roles”,该类型适用于 Microsoft Entra ID (ME-ID)。 参阅 identity 提供程序文档了解详细信息。

    注意

    MapInboundClaims 必须为大多数 OIDC 提供程序设置为 false,这会阻止重命名声明。

    oidcOptions.MapInboundClaims = false;
    oidcOptions.TokenValidationParameters.NameClaimType = "name";
    oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
    
  • 路径配置:路径必须与向 OIDC 提供程序注册应用程序时配置的重定向 URI 路径(登录回叫路径)和退出登录后重定向路径(退出登录回叫路径)匹配。 在 Microsoft Azure 门户中,路径是在应用注册的“身份验证”边栏选项卡中配置的。 登录路径和退出登录路径都必须注册为重定向 URI。 默认值为 /signin-oidc/signout-callback-oidc

    • CallbackPath:应用的基路径内的请求路径,将在其中返回用户代理。

      在 Entra 或 Microsoft Azure 门户中,设置 Web 平台配置的“重定向 URI”中的路径:

      https://localhost/signin-oidc

      注意

      localhost 地址不需要端口。

    • SignedOutCallbackPath:应用基本路径内的请求路径,从 identity 提供程序退出登录后返回用户代理。

      在 Entra 或 Microsoft Azure 门户中,设置 Web 平台配置的“重定向 URI”中的路径:

      https://localhost/signout-callback-oidc

      注意

      localhost 地址不需要端口。

      注意

      如果使用 Microsoft Identity Web,则提供者当前仅在使用 microsoftonline.com 颁发机构 (https://login.microsoftonline.com/{TENANT ID}/v2.0/) 时重定向回 SignedOutCallbackPath。 如果你可以使用 Microsoft Identity Web 的“通用”颁发机构,则不存在此限制。 有关详细信息,请参阅当颁发机构 URL 包含租户 ID 时 postLogoutRedirectUri 不起作用 (AzureAD/microsoft-authentication-library-for-js #5783)

    • RemoteSignOutPath:在此路径上收到的请求会导致处理程序使用退出登录方案调用退出登录。

      在 Entra 或 Microsoft Azure 门户中,设置“前端通道退出登录 URL”:

      https://localhost/signout-oidc

      注意

      localhost 地址不需要端口。

      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 提供程序都使用默认颁发者验证器,但我们需要考虑使用由 https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration 返回的租户 ID ({TENANT ID}) 参数化的颁发者。 有关详细信息,请参阅 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 的请求被代理到后端 Web API 项目 (MinimalApiJwt)。 Program 文件中的 MapForwarder 添加了使用传出请求的默认配置、自定义转换和默认 HTTP 客户端将与指定模式匹配的 HTTP 请求直接转发到特定目标的功能:
    • 在服务器上呈现 Weather 组件时,该组件使用 ServerWeatherForecaster 通过用户的访问令牌代理天气数据请求。
    • 在客户端上呈现该组件时,该组件使用 ClientWeatherForecaster 服务实现,而该实现使用预配置的 HttpClient(在客户端项目的 Program 文件中)对服务器项目进行 Web API 调用。 服务器项目的 Program 文件中定义的最小 API 终结点 (/weather-forecast) 使用用户的访问令牌转换请求以获取天气数据。
  • 借助自定义 cookie 刷新器 (CookieOidcRefresher.cs) 进行的自动非交互式令牌刷新。
  • PersistingAuthenticationStateProvider 类 (PersistingAuthenticationStateProvider.cs) 是服务器端 AuthenticationStateProvider,它使用 PersistentComponentState 将身份验证状态传输到客户端,然后该状态在 WebAssembly 应用程序的生存期内得到修复。
  • 对 Blazor Web App 的请求被代理到后端 Web API 项目 (MinimalApiJwt)。 Program 文件中的 MapForwarder 添加了使用传出请求的默认配置、自定义转换和默认 HTTP 客户端将与指定模式匹配的 HTTP 请求直接转发到特定目标的功能:
    • 在服务器上呈现 Weather 组件时,该组件使用 ServerWeatherForecaster 通过用户的访问令牌代理天气数据请求。
    • 在客户端上呈现该组件时,该组件使用 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) 的请求会被代理到 MinimalApiJwt 项目。

配置

在项目的 Program 文件中,在 AddJwtBearer 调用的 JwtBearerOptions 中配置项目:

  • Audience:设置任何收到的 OpenID Connect 令牌的“受众”。

    在 Azure 或 Entra 门户中:将该值与在“公开 API”下添加 Weather.Get 范围时配置的“应用程序 ID URI”的路径匹配:

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

    示例:

    App ID URI ({APP ID URI}):https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}

    • 目录名称 ({DIRECTORY NAME}):contoso
    • 应用程序(客户端)ID ({CLIENT ID}):00001111-aaaa-2222-bbbb-3333cccc4444
    jwtOptions.Audience = "https://contoso.onmicrosoft.com/00001111-aaaa-2222-bbbb-3333cccc4444";
    

    前面的示例与在 AAD B2C 租户类型的租户中注册的应用相关。 如果应用在 ME-ID 租户中注册,则应用 ID URI 不同,因此受众不同。

    示例:

    应用 ID URI ({APP ID URI}):api://{CLIENT ID},应用程序(客户端)ID ({CLIENT ID}):00001111-aaaa-2222-bbbb-3333cccc4444

    jwtOptions.Audience = "api://00001111-aaaa-2222-bbbb-3333cccc4444";
    
  • Authority:设置用于进行 OpenID Connect 调用的颁发机构。 将值与在 BlazorWebAppOidc/Program.cs 中为 OIDC 处理程序配置的颁发机构进行匹配:

    jwtOptions.Authority = "{AUTHORITY}";
    

    示例:

    颁发机构 ({AUTHORITY}):https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/(使用租户 ID aaaabbbb-0000-cccc-1111-dddd2222eeee

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

    前面的示例与在 AAD B2C 租户类型的租户中注册的应用相关。 如果应用在 ME-ID 租户中注册,则颁发机构应与 identity 提供程序返回的 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] 属性添加到控制器或操作中。

在注销时重定向到 home 页

当用户在应用中导航时,LogInOrOut 组件 (Layout/LogInOrOut.razor) 会将返回 URL (ReturnUrl) 的隐藏字段设置为当前 URL (currentURL) 的值。 当用户从应用退出登录时,identity 提供程序会使其返回到他们退出登录的页。

如果用户从安全页面注销,则他们在注销后会返回到同一安全页面,但仅通过身份验证过程发送回。 当用户需要频繁切换帐户时,此行为是正常的。 然而,替代的应用规范可能会要求用户在退出登录后返回到应用的 home 页或其他页。 以下示例演示如何将应用的 home 页设置为退出登录操作的返回 URL。

以下示例演示了 LogInOrOut 组件的重要更改。 无需为页面设置home/提供隐藏字段ReturnUrl,因为这是默认路径。 不再实现 IDisposable。 不再注入 NavigationManager。 整个 @code 块已被删除。

Layout/LogInOrOut.razor:

@using Microsoft.AspNetCore.Authorization

<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 @context.User.Identity?.Name
                </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>

加密 nonce

nonce 是一个字符串值,可将客户端的会话与 ID 令牌相关联,以缓解重播攻击

如果在身份验证开发和测试期间收到 nonce 错误,则无论对应用或测试用户所做的更改有多小,都请在每次测试运行时使用新的 InPrivate/Incognito 浏览器会话,因为过时的 cookie 数据可能会导致出现 nonce 错误。 有关详细信息,请参阅 Cookie 和站点数据部分。

当刷新令牌交换为新的访问令牌时,不需要或使用 nonce。 在示例应用中,CookieOidcRefresher (CookieOidcRefresher.cs) 故意将 OpenIdConnectProtocolValidator.RequireNonce 设置为 false

未使用 Microsoft Entra (ME-ID) 注册应用的应用程序角色

本部分与不使用 Microsoft Entra ID (ME-ID) 作为 identity 提供程序的应用相关。 有关使用 ME-ID 注册的应用,请参阅 使用 Microsoft Entra (ME-ID) 注册应用的应用程序角色部分。

Program.csOpenIdConnectOptions 中配置角色声明类型 (TokenValidationParameters.RoleClaimType)

oidcOptions.TokenValidationParameters.RoleClaimType = "{ROLE CLAIM TYPE}";

对于许多 OIDC identity 提供程序,角色声明类型为 role。 检查 identity 提供程序的文档以获取正确的值。

BlazorWebAppOidc.Client 项目中 UserInfo 类替换为以下类。

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 内置 Administrator 角色。

本部分中介绍的方法配置 ME-ID 以在身份验证 cookie 标头中发送组和角色。 如果用户只是少数安全组和角色的成员,则以下方法应该适用于大多数托管平台,而不会遇到标头过长的问题,例如,具有默认标头长度限制为 16 KB 的 (MaxRequestBytes) IIS 托管。 如果标头长度由于组或角色成员身份较高而出现问题,我们建议不要遵循本节中的指导,而是支持实现 Microsoft Graph 单独从 ME-ID 获取用户的组和角色,这种方法不会夸大身份验证 cookie 的大小。 有关详细信息,请参阅错误请求 - 请求太长 - IIS 服务器 (dotnet/aspnetcore #57545)

Program.csOpenIdConnectOptions 中配置角色声明类型 (TokenValidationParameters.RoleClaimType)。 将值设置为 roles

oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

尽管没有 ME-ID Premium 帐户就无法将角色分配给组,但你可以将角色分配给用户,并为具有标准 Azure 帐户的用户接收角色声明。 本部分中的指南不需要 ME-ID Premium 帐户。

使用默认目录时,请按照将应用角色添加到应用程序并在令牌(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. 选择“保存”按钮。

BlazorWebAppOidc.Client 项目中 UserInfo 类替换为以下类。

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 声明中,每个角色一个声明。 具有 b79fbf4d-3ef9-4689-8143-76b194e85509 值的 wids 声明始终由租户的非来宾帐户的 ME-ID 发送,并且不引用管理员角色。 选择“角色和管理员”时,管理员角色 GUID(角色模板 ID)显示在 Azure 门户中,后跟省略号 () > 列出的角色的说明。 角色模板 ID 也列在 Microsoft Entra 内置角色(Entra 文档)中。

疑难解答

日志记录

服务器应用是一个标准 ASP.NET Core 应用。 请参阅 ASP.NET Core 记录指南,以便在服务器应用中启用较低的记录级别。

若要为 Blazor WebAssembly 身份验证启用调试或跟踪日志记录,请参阅 ASP.NET CoreBlazor 日志记录客户端身份验证日志记录部分,其中项目版本选择器设置为 ASP.NET Core 7.0 或更高版本。

常见错误

  • 应用或 Identity 提供者 (IP) 配置错误

    最常见的错误是因为配置不正确导致的。 下面是几个示例:

    • 根据具体情景的要求,缺少或不正确的颁发机构、实例、租户 ID、租户域、客户端 ID 或重定向 URI 会阻止应用对客户端进行身份验证。
    • 不正确的请求范围会阻止客户端访问服务器 Web API 终结点。
    • 服务器 API 权限不正确或缺失会阻止客户端访问服务器 Web API 终结点。
    • 在不同于 IP 应用注册的重定向 URI 中配置的应用的端口运行应用。 请注意,Microsoft Entra ID 和在 localhost 开发测试地址上运行的应用不需要端口,但应用的端口配置和运行应用的端口必须与非 localhost 地址匹配。

    本文中的配置部分包含正确配置的示例。 请仔细查看配置,以查找应用和 IP 配置错误。

    如果配置看起来是正确的:

    • 分析应用程序日志。

    • 通过浏览器的开发人员工具,检查客户端应用和 IP 或服务器应用之间的网络流量。 通常,在发出请求后,IP 或服务器应用会向客户端返回一条确切的错误消息或包含线索的消息,其中指出了导致问题的原因。 有关开发人员工具指导,请参阅以下文章:

    文档团队会响应文章中的文档反馈和 bug(从“此页面”反馈部分提交问题),但无法提供产品支持。 可以借助多个公共支持论坛来帮助排查应用问题。 建议如下:

    上述论坛并非 Microsoft 所拥有或者不受 Microsoft 控制。

    对于非安全、非敏感且非机密的可重现框架 bug 报告,请向 ASP.NET Core 产品团队提交问题。 请务必先彻底调查问题原因,并确定无法自行解决问题,在公共支持论坛的社区帮助下同样无法解决问题后,再向该产品团队提交问题。 如果应用问题是由简单的配置错误引起或涉及第三方服务,该产品团队无法对此进行故障排除。 如果报告包含敏感或机密内容,或者描述了可能会被网络攻击者利用的潜在产品安全缺陷,请参阅报告安全问题和 bug(dotnet/aspnetcore GitHub 存储库)

  • ME-ID 的客户端未获得授权

    信息: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 中使用自定义命令以 InPrivate 或无痕模式打开浏览器:
    • 通过 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 Core SDK 或在应用内更改包版本后可能会立即出现故障。 在某些情况下,不同的包可能在执行主要升级时中断应用。 可以按照以下说明来修复其中大部分问题:

  1. 从命令 shell 执行 dotnet nuget locals all --clear 以清空本地系统的 NuGet 包缓存。
  2. 删除项目的 binobj 文件夹。
  3. 还原并重新生成项目。
  4. 在重新部署应用前,在服务器上删除部署文件夹中的所有文件。

注意

不支持使用与应用的目标框架不兼容的包版本。 有关包的信息,请使用 NuGet GalleryFuGet Package Explorer 进行了解。

运行服务器应用

在对 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;
    }
}

其他资源