使用 Microsoft Entra ID 保护 ASP.NET Core Blazor Web App

本文介绍如何使用示例应用通过 Microsoft 标识平台和面向 Microsoft Entra IDMicrosoft Identity Web 程序包保护 Blazor Web App。

本文的此版本介绍了如何实现 Entra,而无需 采用前端后端 (BFF) 模式。 BFF 模式是一种用于向外部服务发出经过身份验证请求的设计模式。 如果应用的规范要求采用 BFF 模式 ,请将项目版本选择器更改为 BFF 模式。

涵盖以下规范:

  • Blazor Web App 使用具有全局交互性的自动呈现模式(InteractiveAuto
  • 服务器项目调用 AddAuthenticationStateSerialization 来添加一个服务器端身份验证状态提供程序,该程序使用 PersistentComponentState 将身份验证状态传递给客户端。 客户端调用 AddAuthenticationStateDeserialization 反序列化并使用服务器传递的身份验证状态。 身份验证状态在 WebAssembly 应用程序的生存期内是固定的。
  • 应用程序使用基于 包的 Identity。
  • 自动非交互式令牌刷新由框架管理。
  • 应用使用服务器端和客户端服务抽象来显示生成的天气数据:
    • 在服务器上呈现 Weather 组件以显示天气数据时,该组件使用 ServerWeatherForecaster. Microsoft Identity Web 包提供 API 来创建用于进行 Web API 调用的命名下游 Web 服务。 IDownstreamApi 被注入到 ServerWeatherForecaster 中,用于调用 CallApiForUserAsync 从外部 Web API(MinimalApiJwt 项目)获取天气数据。
    • Weather组件在客户端呈现时,该组件使用ClientWeatherForecaster服务实现,此服务使用预配置的HttpClient(位于客户端项目的Program文件中)进行Web API调用,以向服务器项目的Minimal API(/weather-forecast)请求天气数据。 最小 API 终结点从 ServerWeatherForecaster 类获取天气数据,并将其返回到客户端供组件呈现。

示例解决方案

示例解决方案包括以下项目:

  • BlazorWebAppEntra:Blazor Web App 的服务器端项目,包含天气数据的示例Minimal API 端点。
  • BlazorWebAppEntra.Client:Blazor Web App 的客户端项目。
  • MinimalApiJwt:后台 Web API,包含一个用于天气数据的Minimal API示例端点。

使用以下链接通过示例存储库中的 Blazor 最新版本文件夹访问示例。 此示例位于 BlazorWebAppEntra .NET 9 或更高版本的文件夹中。

Aspire/Aspire.AppHost 项目启动解决方案。

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

Microsoft Entra ID 应用注册

我们建议对应用和 Web API 使用单独的注册,即使应用和 Web API 位于同一解决方案中也是如此。 以下指南适用于 BlazorWebAppEntra 示例解决方案的应用和 MinimalApiJwt Web API,但相同的指南通常适用于应用和 Web API 的任何基于 Entra 的注册。

首先注册 Web API(MinimalApiJwt),以便在注册应用时授予对 Web API 的访问权限。 Web API 的租户 ID 和客户端 ID 用于在其 Program 文件中配置 Web API。 注册 Web API 后,在应用注册>中公开 Web API公开具有作用域名称的 Weather.Get 的 API。 记录应用 ID URI,以便在应用的配置中使用。

接下来,使用 Web 平台配置和重定向 URI https://localhost/signin-oidc 注册应用 (BlazorWebAppEntra)(不需要端口)。 应用的租户 ID、租户域和客户端 ID 以及 Web API 的基址、应用 ID URI 和天气范围名称用于在其 appsettings.json 文件中配置应用。 授予 API 权限,以在 应用注册>API 权限中访问 Web API。 如果应用的安全规范要求它,可以授予组织访问 Web API 的管理员同意。 授权用户和组被分配到应用注册>企业应用程序中的应用注册。

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

在 Entra 或 Azure 门户的应用注册中创建客户端密码(管理>证书和机密>新客户端密码)。 请保留客户端机密,以在下一部分中使用。

本文后面提供了有关特定设置的其他 Entra 配置指南。

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

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

客户端 Blazor Web App 项目 (BlazorWebAppEntra.Client)

BlazorWebAppEntra.Client 项目是 Blazor Web App 的客户端项目。

如果用户需要在客户端呈现期间登录或注销,则会启动完整页面重新加载。

后端 Web API 项目 (MinimalApiJwt)

MinimalApiJwt 项目是用于多个前端项目的后端 Web API。 该项目为天气数据配置了一个最小 API 终结点。

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

该项目包括用于在开发环境中生成 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] 属性添加到控制器或操作中。

配置后端 Web API 项目 (MinimalApiJwt

MinimalApiJwt 项目的 Program 文件中,在 AddJwtBearer 调用的 JwtBearerOptions 中配置该项目。

对于 Web API 应用的注册,范围 Weather.Get 是在 “公开 API”的 Entra 或 Azure 门户中配置的。

Authority 设置颁发机构以便进行 OIDC 调用。

jwtOptions.Authority = "{AUTHORITY}";

以下示例使用租户 ID aaaabbbb-0000-cccc-1111-dddd2222eeee

如果应用在 ME-ID 租户中注册,则颁发机构应与标识提供者返回的 JWT 的颁发者 (iss) 匹配:

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 为收到的任何 JWT 访问令牌设置访问群体。

jwtOptions.Audience = "{AUDIENCE}";

在 Entra 或 Azure 门户中,将值与公开 API 下添加 Weather.Get 范围时配置的 应用程序 ID URI 的路径匹配。 不要在值中包含范围名称“Weather.Get”“。

以下示例使用应用程序(客户端)ID 为 11112222-bbbb-3333-cccc-4444dddd5555。 第二个示例使用租户域contoso.onmicrosoft.com

ME-ID 租户示例:

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

AAD B2C 租户示例:

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

配置服务器项目 (BlazorWebAppEntra

Microsoft Identity WebMicrosoft.Identity.Web NuGet 包API 文档)中的 AddMicrosoftIdentityWebApp 是在 BlazorWebAppEntra 项目的 Program 文件中配置。

从 Entra 或 Azure 门户中的应用注册中获取应用程序(客户端)ID、租户(发布者)域和目录(租户)ID。 从 Web API 的注册中获取用于 Weather.Get 范围的应用 ID URI。 从门户获取应用 ID URI 时,不要包含范围名称。

BlazorWebAppEntra 项目的 Program 文件中,在 Microsoft Identity Web 配置中提供以下占位符的值:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
        msIdentityOptions.Domain = "{DIRECTORY NAME}.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "{TENANT ID}";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "{BASE ADDRESS}";
        configOptions.Scopes = [ "{APP ID URI}/Weather.Get" ];
    })
    .AddDistributedTokenCaches();

前面配置中的占位符:

  • {CLIENT ID (BLAZOR APP)}:应用程序(客户端)ID。
  • {DIRECTORY NAME}:租户(发布者)域的目录名称。
  • {TENANT ID}:目录(租户)ID。
  • {BASE ADDRESS}:Web API 的基址。
  • {APP ID URI}:Web API 范围的应用 ID URI。 使用以下格式之一,其中{CLIENT ID (WEB API)}占位符是 Entra 注册的客户端 ID,而{DIRECTORY NAME}占位符是租户(发布者)域的目录名称(例如:contoso)。
    • ME-ID 租户格式:api://{CLIENT ID (WEB API)}
    • B2C 租户格式:https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

示例:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
        msIdentityOptions.Domain = "contoso.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "https://localhost:7277";
        configOptions.Scopes = [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ];
    })
    .AddDistributedTokenCaches();

本文的此版本介绍如何使用 前端后端 (BFF) 模式实现 Entra。 如果应用的规范不要求采用 BFF 模式,请将文章版本选择器更改为 非 BFF 模式。

涵盖以下规范:

  • Blazor Web App 使用具有全局交互性的自动呈现模式(InteractiveAuto
  • 服务器项目调用 AddAuthenticationStateSerialization 来添加一个服务器端身份验证状态提供程序,该程序使用 PersistentComponentState 将身份验证状态传递给客户端。 客户端调用 AddAuthenticationStateDeserialization 反序列化并使用服务器传递的身份验证状态。 身份验证状态在 WebAssembly 应用程序的生存期内是固定的。
  • 应用程序使用基于 包的 Identity。
  • 自动非交互式令牌刷新由框架管理。
  • 采用服务于前端的后端 (BFF) 模式,使用 .NET Aspire 进行服务发现,使用 YARP 将请求代理到后端应用上的天气预报终结点。
    • 后端 Web API 使用 JWT 持有者身份验证来验证登录 Blazor Web App 中的 cookie 保存的 JWT 令牌。
    • Aspire 改善了生成 .NET 云原生应用的体验。 它提供了一套一致的、固定的工具和模式,用于生成和运行分布式应用。
    • YARP (Yet Another Reverse Proxy) 是一个用于创建反向代理服务器的库。
  • 应用使用服务器端和客户端服务抽象来显示生成的天气数据。
    • 在服务器上呈现 Weather 组件以显示天气数据时,该组件使用 ServerWeatherForecaster. Microsoft Identity Web 包提供 API 来创建用于进行 Web API 调用的命名下游 Web 服务。 IDownstreamApi 被注入到 ServerWeatherForecaster 中,用于调用 CallApiForUserAsync 从外部 Web API(MinimalApiJwt 项目)获取天气数据。
    • Weather组件在客户端呈现时,该组件使用ClientWeatherForecaster服务实现,此服务使用预配置的HttpClient(位于客户端项目的Program文件中)进行Web API调用,以向服务器项目的Minimal API(/weather-forecast)请求天气数据。 最小 API 终结点通过调用 GetAccessTokenForUserAsync获取用户的访问令牌。 除了正确的范围外,还向外部 Web API(MinimalApiJwt 项目)发出反向代理调用,以便获取天气数据并将其返回到客户端供组件呈现。

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

先决条件

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

另请参阅快速入门的 “先决条件 ”部分 :生成第一个 .NET Aspire 应用

示例解决方案

示例解决方案包括以下项目:

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

使用以下链接通过示例存储库中的 Blazor 最新版本文件夹访问示例。 此示例位于 BlazorWebAppEntraBff .NET 9 或更高版本的文件夹中。

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

Microsoft Entra ID 应用注册

我们建议对应用和 Web API 使用单独的注册,即使应用和 Web API 位于同一解决方案中也是如此。 以下指南适用于 BlazorWebAppEntra 示例解决方案的应用和 MinimalApiJwt Web API,但相同的指南通常适用于应用和 Web API 的任何基于 Entra 的注册。

首先注册 Web API(MinimalApiJwt),以便在注册应用时授予对 Web API 的访问权限。 Web API 的租户 ID 和客户端 ID 用于在其 Program 文件中配置 Web API。 注册 Web API 后,在应用注册>中公开 Web API公开具有作用域名称的 Weather.Get 的 API。 记录应用 ID URI,以便在应用的配置中使用。

接下来,使用 Web 平台配置和重定向 URI https://localhost/signin-oidc 注册应用 (BlazorWebAppEntra)(不需要端口)。 应用的租户 ID、租户域和客户端 ID 以及 Web API 的基址、应用 ID URI 和天气范围名称用于在其 appsettings.json 文件中配置应用。 授予 API 权限,以在 应用注册>API 权限中访问 Web API。 如果应用的安全规范要求它,可以授予组织访问 Web API 的管理员同意。 授权用户和组被分配到应用注册>企业应用程序中的应用注册。

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

在 Entra 或 Azure 门户的应用注册中创建客户端密码(管理>证书和机密>新客户端密码)。 请保留客户端机密,以在下一部分中使用。

本文后面提供了有关特定设置的其他 Entra 配置指南。

.NET Aspire 项目

若要详细了解如何使用 .NET Aspire 以及详细了解示例应用的 .AppHost.ServiceDefaults 项目,请参阅 .NET Aspire 文档

确认已满足 .NET Aspire 的先决条件。 有关详细信息,请参阅快速入门:构建您的第一个 应用程序的先决条件部分。

示例应用仅配置一个不安全的 HTTP 启动配置文件 (http),供在开发测试期间使用。 有关详细信息,包括不安全和安全启动设置配置文件的示例,请参阅 在 .NET Aspire 中允许不安全传输(.NET Aspire 文档)

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

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

客户端 Blazor Web App 项目 (BlazorWebAppEntra.Client)

BlazorWebAppEntra.Client 项目是 Blazor Web App 的客户端项目。

如果用户需要在客户端呈现期间登录或注销,则会启动完整页面重新加载。

后端 Web API 项目 (MinimalApiJwt)

MinimalApiJwt 项目是用于多个前端项目的后端 Web API。 该项目为天气数据配置了一个最小 API 终结点。 来自 Blazor Web App 服务器端项目 (BlazorWebAppEntra) 的请求会被代理到 MinimalApiJwt 项目。

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

该项目包括用于在开发环境中生成 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] 属性添加到控制器或操作中。

配置后端 Web API 项目 (MinimalApiJwt

MinimalApiJwt 项目的 Program 文件中,在 AddJwtBearer 调用的 JwtBearerOptions 中配置该项目。

对于 Web API 应用的注册,范围 Weather.Get 是在 “公开 API”的 Entra 或 Azure 门户中配置的。

Authority 设置颁发机构以便进行 OIDC 调用。

jwtOptions.Authority = "{AUTHORITY}";

以下示例使用租户 ID aaaabbbb-0000-cccc-1111-dddd2222eeee

如果应用在 ME-ID 租户中注册,则颁发机构应与标识提供者返回的 JWT 的颁发者 (iss) 匹配:

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 为收到的任何 JWT 访问令牌设置访问群体。

jwtOptions.Audience = "{AUDIENCE}";

在 Entra 或 Azure 门户中,将值与公开 API 下添加 Weather.Get 范围时配置的 应用程序 ID URI 的路径匹配。 不要在值中包含范围名称“Weather.Get”“。

以下示例使用应用程序(客户端)ID 为 11112222-bbbb-3333-cccc-4444dddd5555。 第二个示例使用租户域contoso.onmicrosoft.com

ME-ID 租户示例:

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

AAD B2C 租户示例:

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

配置服务器项目 (BlazorWebAppEntra

Microsoft Identity WebMicrosoft.Identity.Web NuGet 包API 文档)中的 AddMicrosoftIdentityWebApp 是在 BlazorWebAppEntra 项目的 Program 文件中配置。

从 Entra 或 Azure 门户中的应用注册中获取应用程序(客户端)ID、租户(发布者)域和目录(租户)ID。 从 Web API 的注册中获取用于 Weather.Get 范围的应用 ID URI。 从门户获取应用 ID URI 时,不要包含范围名称。

BlazorWebAppEntra 项目的 Program 文件中,在 Microsoft Identity Web 配置中提供以下占位符的值:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
        msIdentityOptions.Domain = "{DIRECTORY NAME}.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "{TENANT ID}";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "{BASE ADDRESS}";
        configOptions.Scopes = [ "{APP ID URI}/Weather.Get" ];
    })
    .AddDistributedTokenCaches();

前面配置中的占位符:

  • {CLIENT ID (BLAZOR APP)}:应用程序(客户端)ID。
  • {DIRECTORY NAME}:租户(发布者)域的目录名称。
  • {TENANT ID}:目录(租户)ID。
  • {BASE ADDRESS}:Web API 的基址。
  • {APP ID URI}:Web API 范围的应用 ID URI。 使用以下格式之一,其中{CLIENT ID (WEB API)}占位符是 Entra 注册的客户端 ID,而{DIRECTORY NAME}占位符是租户(发布者)域的目录名称(例如:contoso)。
    • ME-ID 租户格式:api://{CLIENT ID (WEB API)}
    • B2C 租户格式:https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

示例:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
        msIdentityOptions.Domain = "contoso.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "https://localhost:7277";
        configOptions.Scopes = [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ];
    })
    .AddDistributedTokenCaches();

警告

生产应用应使用生产分布式令牌缓存提供程序。 否则,在某些情况下,应用的性能可能不佳。 有关详细信息,请参阅 “使用生产分布式令牌缓存提供程序 ”部分。

回调路径 (CallbackPath) 必须与在 Entra 或 Azure 门户 中注册应用程序时配置的重定向 URI(登录回调路径)匹配。 路径在应用注册的身份验证边栏选项卡中配置。 对于已注册的重定向 URI CallbackPath,默认值 /signin-oidchttps://localhost/signin-oidc(不需要端口)。

SignedOutCallbackPath 是 OpenID Connect 处理程序拦截的应用基路径中的请求路径,用户代理在从 Entra 注销后首先返回该路径。 示例应用不会为路径设置值,因为使用了默认值“/signout-callback-oidc”。 截获请求后,OpenID Connect 处理程序会重定向到 SignedOutRedirectUriRedirectUri(如果指定)。

在应用的 Entra 提供程序注册中配置用户登出回调路径。 在 Entra 或 Azure 门户中,在 Web 平台配置的 重定向 URI 条目中设置路径:

https://localhost/signout-callback-oidc

注意

使用 Entra 时,localhost 地址不需要端口。

如果不将注销回调路径 URI 添加到 Entra 中应用的注册中,Entra 将拒绝将用户重定向回应用程序,只会要求他们关闭浏览器窗口。

注意

Entra 不会将主管理员用户(根帐户)或外部用户重定向回 Blazor 应用程序。 相反,Entra 会将用户从应用注销,并建议他们关闭其所有浏览器窗口。 有关详细信息,请参阅当授权 URL 中包含租户 ID 时 postLogoutRedirectUri 不起作用 (AzureAD/microsoft-authentication-library-for-js #5783)

警告

不要在客户端代码中存储应用机密、连接字符串、凭据、密码、个人标识号(PIN)、专用 C#/.NET 代码或私钥/令牌,这始终不安全 在测试/暂存和生产环境中,服务器端 Blazor 代码和 Web API 应使用安全身份验证流,以避免在项目代码或配置文件中维护凭据。 在本地开发测试之外,我们建议避免使用环境变量来存储敏感数据,因为环境变量不是最安全的方法。 对于本地开发测试, 建议使用机密管理器工具 来保护敏感数据。 有关详细信息,请参阅 安全维护敏感数据和凭据

建立客户端密码

本部分仅适用于 Blazor Web App 服务器项目。

使用以下任一方法或两种方法向应用提供客户端密码:

  • 机密管理器工具:机密管理器工具将专用数据存储在本地计算机上,仅在本地开发期间使用。
  • Azure 密钥库:可以在密钥保管库中存储客户端机密,以便在任何环境中使用,包括在本地工作时用于开发环境。 一些开发人员更喜欢使用密钥保管库进行过渡和生产部署,并使用机密管理器工具进行本地开发。

强烈建议避免将客户端机密存储在项目代码或配置文件中。 使用安全身份验证流,例如本部分中的任一或两种方法。

机密管理器工具

机密 管理器工具 可以将服务器应用的客户端密码存储在配置密钥 AzureAd:ClientSecret下。

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

dotnet user-secrets init

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

dotnet user-secrets set "AzureAd:ClientSecret" "{SECRET}"

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

Azure Key Vault

Azure 密钥库提供了一种安全的方法,用于向应用提供应用的客户端密码。

若要创建密钥保管库并设置客户端密钥,请参阅关于 Azure 密钥保管库机密 (Azure 文档),其中交叉链接了开始使用 Azure 密钥保管库的资源。 若要在本部分中实现代码,请在创建密钥保管库和机密时记录 Azure 中的密钥保管库 URI 和机密名称。 对于本部分中的示例,机密名称为“BlazorWebAppEntraClientSecret.”

在 Entra 或 Azure 门户中建立密钥保管库时:

  • 将密钥保管库配置为使用 Azure 基于角色的访问控制(RABC)。 如果您未在 Azure 虚拟网络上运行(包括用于本地开发和测试),请确认在网络步骤中,公共访问已启用(已选中)。 启用公共访问仅会公开密钥保管库终结点。 访问仍需要经过身份验证的帐户。

  • 使用“密钥保管库机密用户”角色创建 Azure 托管 Identity(或向计划使用的现有托管 Identity 添加角色)。 将托管Identity分配给托管部署的 Azure 应用服务:设置>Identity>用户分配>添加

    注意

    如果还计划通过授权用户使用 Azure CLI 或 Visual Studio 的 Azure 服务身份验证在本地运行用于 Blob 访问的应用,请在 访问控制(IAM) 中为开发人员 Azure 用户帐户添加 Key Vault 机密用户 角色。 若要通过 Visual Studio 使用 Azure CLI,请从开发人员 PowerShell 面板执行 az login 该命令,并按照提示向租户进行身份验证。

若要实现本节中的代码,在创建密钥保管库和机密时,请从 Azure 记录密钥保管库的 URI(例如:“https://contoso.vault.azure.net/”,需要尾部反斜杠)和机密名称(例如:“BlazorWebAppEntraClientSecret”)。

重要

密钥保管库机密创建时设置了过期日期。 请务必跟踪密钥保管库机密何时过期,并在该日期之前为应用创建新的机密。

将以下 AzureHelper 类添加到服务器项目。 GetKeyVaultSecret 方法从密钥保管库中检索机密。 调整命名空间 (BlazorSample.Helpers) 以匹配项目命名空间方案。

Helpers/AzureHelper.cs:

using Azure.Core;
using Azure.Security.KeyVault.Secrets;

namespace BlazorWebAppEntra.Helpers;

public static class AzureHelper
{
    public static string GetKeyVaultSecret(string vaultUri, 
        TokenCredential credential, string secretName)
    {
        var client = new SecretClient(new Uri(vaultUri), credential);
        var secret = client.GetSecretAsync(secretName).Result;

        return secret.Value.Value;
    }
}

注意

前面的示例用于 DefaultAzureCredential 简化身份验证,同时开发部署到 Azure 的应用,方法是将 Azure 托管环境中使用的凭据与本地开发中使用的凭据组合在一起。 转移到生产环境时,可以选择更好的替代方案,例如 ManagedIdentityCredential。 有关详细信息,请参阅 使用系统分配的托管标识向 Azure 资源验证 Azure 托管的 .NET 应用

如果服务在服务器项目的 Program 文件中注册,请使用以下代码获取并应用客户端密码:

TokenCredential? credential;

if (builder.Environment.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

设置了MicrosoftIdentityOptions的地方,调用GetKeyVaultSecret以接收和分配应用程序的客户端密钥:

msIdentityOptions.ClientSecret = AzureHelper.GetKeyVaultSecret("{VAULT URI}", 
    credential, "{SECRET NAME}");

{MANAGED IDENTITY CLIENT ID}:Azure 托管 Identity 客户端 ID (GUID)。

{TENANT ID}:目录(租户)ID。 示例:aaaabbbb-0000-cccc-1111-dddd2222eeee

{VAULT URI}:密钥保管库 URI。 包括 URI 最后的反斜杠。 示例:https://contoso.vault.azure.net/

{SECRET NAME}:机密名称。 示例:BlazorWebAppEntraClientSecret

配置用于根据应用的环境配置文件提供专用密钥保管库和机密名称。 例如,可以在开发中提供不同的配置值appsettings.Development.json,在暂存时提供appsettings.Staging.json,以及在生产部署提供appsettings.Production.json。 有关详细信息,请参阅 ASP.NET Core Blazor 配置

仅序列化名称和角色声明

Program文件中,所有声明都通过将SerializeAllClaims设置为true来进行序列化。 如果只想为 CSR 序列化名称和角色声明,请删除该选项或将其设置为 false

使用 JSON 配置提供程序提供配置(应用设置)

示例解决方案项目在其 Identity 文件中配置 Microsoft Program Web 和 JWT 持有者身份验证,以便使用 C# 自动完成来发现配置设置。 专业应用通常使用 配置提供程序 来配置 OIDC 选项,例如默认 JSON 配置提供程序。 JSON 配置提供程序从应用设置文件 appsettings.json/appsettings.{ENVIRONMENT}.json加载配置,其中 {ENVIRONMENT} 占位符是应用的 运行时环境。 按照本部分中的指南使用应用设置文件进行配置。

在项目的应用设置文件(appsettings.jsonBlazorWebAppEntra 中添加以下 JSON 配置:

{
  "AzureAd": {
    "CallbackPath": "/signin-oidc",
    "ClientId": "{CLIENT ID (BLAZOR APP)}",
    "Domain": "{DIRECTORY NAME}.onmicrosoft.com",
    "Instance": "https://login.microsoftonline.com/",
    "ResponseType": "code",
    "TenantId": "{TENANT ID}"
  },
  "DownstreamApi": {
    "BaseUrl": "{BASE ADDRESS}",
    "Scopes": [ "{APP ID URI}/Weather.Get" ]
  }
}

更新上述配置中的占位符,以匹配应用在 Program 文件中使用的值:

  • {CLIENT ID (BLAZOR APP)}:应用程序(客户端)ID。
  • {DIRECTORY NAME}:租户(发布者)域的目录名称。
  • {TENANT ID}:目录(租户)ID。
  • {BASE ADDRESS}:Web API 的基址。
  • {APP ID URI}:Web API 范围的应用 ID URI。 使用以下格式之一,其中{CLIENT ID (WEB API)}占位符是 Entra 注册的客户端 ID,而{DIRECTORY NAME}占位符是租户(发布者)域的目录名称(例如:contoso)。
    • ME-ID 租户格式:api://{CLIENT ID (WEB API)}
    • B2C 租户格式:https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

示例:

"AzureAd": {
  "CallbackPath": "/signin-oidc",
  "ClientId": "00001111-aaaa-2222-bbbb-3333cccc4444",
  "Domain": "contoso.onmicrosoft.com",
  "Instance": "https://login.microsoftonline.com/",
  "ResponseType": "code",
  "TenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
},
"DownstreamApi": {
  "BaseUrl": "https://localhost:7277",
  "Scopes": [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ]
}

更新上述配置中的任何其他值,以匹配文件中使用的 Program 自定义/非默认值。

身份验证生成器会自动选取配置。

Program 文件中进行以下更改:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
-   .AddMicrosoftIdentityWebApp(msIdentityOptions =>
-   {
-       msIdentityOptions.CallbackPath = "...";
-       msIdentityOptions.ClientId = "...";
-       msIdentityOptions.Domain = "...";
-       msIdentityOptions.Instance = "...";
-       msIdentityOptions.ResponseType = "...";
-       msIdentityOptions.TenantId = "...";
-   })
+   .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
-   .AddDownstreamApi("DownstreamApi", configOptions =>
-   {
-       configOptions.BaseUrl = "...";
-       configOptions.Scopes = [ "..." ];
-   })
+   .AddDownstreamApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
    .AddDistributedTokenCaches();

注意

生产应用应使用生产分布式令牌缓存提供程序。 否则,在某些情况下,应用的性能可能不佳。 有关详细信息,请参阅 “使用生产分布式令牌缓存提供程序 ”部分。

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 的租户 ID。
  • {APP ID URI (WEB API)}:Web API 的应用 ID URI。

颁发机构格式采用以下模式:

  • ME-ID 租户类型: https://sts.windows.net/{TENANT ID}/
  • B2C 租户类型:https://login.microsoftonline.com/{TENANT ID}/v2.0/

受众格式采用以下模式({CLIENT ID} 是 Web API 的客户端 ID; {DIRECTORY NAME} 例如,目录名称 contoso):

  • ME-ID 租户类型: api://{CLIENT ID}
  • B2C 租户类型:https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}

JWT 持有者身份验证生成器会自动选取配置。

Program 文件中删除以下行:

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

有关配置的详细信息,请参阅以下资源:

使用用于生产环境的分布式令牌缓存提供程序

调用 AddDistributedTokenCaches 时会创建内存中分布式令牌缓存,以确保有一个可用于分布式令牌缓存的基本实现。

生产 Web 应用和 Web API 应使用生产分布式令牌缓存(例如: RedisMicrosoft SQL ServerMicrosoft Azure Cosmos DB)。

注意

对于单台计算机上的本地开发和测试,可以使用内存中令牌缓存,而不是分布式令牌缓存:

builder.Services.AddInMemoryTokenCaches();

在开发和测试期间晚些时候,采用生产分布式令牌缓存提供程序。

AddDistributedMemoryCache 添加了 IDistributedCache 的默认实现,该实现将在内存中存储缓存项,Microsoft Identity Web 使用此功能进行令牌缓存。

分布式令牌缓存由以下方式 MsalDistributedTokenCacheAdapterOptions配置:

builder.Services.AddDistributedMemoryCache();

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    options => 
    {
      // The following lines that are commented out reflect
      // default values. We recommend overriding the default
      // value of Encrypt to encrypt tokens at rest.

      //options.DisableL1Cache = false;
      //options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024;
      options.Encrypt = true;
      //options.SlidingExpiration = TimeSpan.FromHours(1);
    });

AddDistributedMemoryCache 需要对 Microsoft.Extensions.Caching.Memory NuGet 包的包引用。

注意

有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。

若要配置生产分布式缓存提供程序,请参阅 ASP.NET Core 中的分布式缓存

警告

将应用部署到生产环境时,始终将内存中分布式令牌缓存替换为真正的令牌缓存提供程序。 如果无法采用生产分布式令牌缓存提供程序,应用的性能可能会显著下降。

有关详细信息,请参阅 令牌缓存序列化:分布式缓存。 但是,所示的代码示例不适用于 ASP.NET 核心应用,这些应用通过(而不是AddDistributedMemoryCacheAddDistributedTokenCache配置分布式缓存。

在生产环境中使用共享数据保护密钥环,以便当 MsalDistributedTokenCacheAdapterOptions.Encrypt 设置为 true 时,Web 场中跨服务器的应用实例可以解密令牌。

注意

若要在单个计算机上进行早期开发和本地测试,可以稍后设置Encryptfalse和配置共享数据保护密钥环:

options.Encrypt = false;

稍后在开发和测试期间,启用令牌加密并采用共享数据保护密钥环。

以下示例演示如何对共享密钥环使用 Azure Blob 存储和 Azure Key Vault(PersistKeysToAzureBlobStorage/ProtectKeysWithAzureKeyVault)。 服务配置是用于演示的基本方案。 在部署生产应用之前,请熟悉 Azure 服务,并使用 Azure 服务的专用文档集采用最佳做法,这些文档集在本部分末尾链接。

请确认以下包是否存在于 Blazor Web App 的服务器项目中:

注意

有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。

注意

在继续执行以下步骤之前,请确认应用已注册到 Microsoft Entra。

通常,在实现 生产分布式令牌缓存提供程序 的同时实现以下代码。 Azure 和 Azure 外部的其他选项可用于跨多个应用实例管理数据保护密钥,但示例应用演示如何使用 Azure 服务。

配置 Azure Blob 存储以维护数据保护密钥。 按照 ASP.NET Core 中密钥存储提供程序中的指南进行操作。

配置 Azure Key Vault 以加密静态数据保护密钥。 按照 配置 ASP.NET 核心数据保护中的指南进行作。

Program 注册服务的文件中使用以下代码:

TokenCredential? credential;

if (builder.Environment.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

builder.Services.AddDataProtection()
    .SetApplicationName("BlazorWebAppEntra")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential)
    .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential);

可以将任何应用名称传递给 SetApplicationName。 只需确认所有应用部署都使用相同的值。

{MANAGED IDENTITY CLIENT ID}:Azure 托管 Identity 客户端 ID (GUID)。

{TENANT ID}:租户 ID。

{BLOB URI}:密钥文件的完整 URI。 创建密钥文件时,Azure 存储会生成 URI。 请勿使用 SAS。

{KEY IDENTIFIER}:用于密钥加密的 Azure Key Vault 密钥标识符。 访问策略允许应用程序使用GetUnwrap KeyWrap Key权限访问密钥保管库。 在创建密钥后,可以从 Entra 或 Azure 门户中的密钥获取其版本。 如果启用密钥保管库密钥的自动轮转,请确保在应用的密钥保管库配置中使用无版本密钥标识符,其中未在标识符末尾放置任何密钥 GUID(例如: https://contoso.vault.azure.net/keys/data-protection

注意

在非生产环境中,前面的示例用于 DefaultAzureCredential 简化身份验证,同时开发部署到 Azure 的应用,方法是将 Azure 托管环境中使用的凭据与本地开发中使用的凭据组合在一起。 有关详细信息,请参阅 使用系统分配的托管标识向 Azure 资源验证 Azure 托管的 .NET 应用

或者,可以将应用配置为通过 JSON 配置提供程序从应用设置文件中提供值。 将以下内容添加到应用设置文件:

"DistributedTokenCache": {
  "DisableL1Cache": false,
  "L1CacheSizeLimit": 524288000,
  "Encrypt": true,
  "SlidingExpirationInHours": 1
},
"DataProtection": {
  "BlobUri": "{BLOB URI}",
  "KeyIdentifier": "{KEY IDENTIFIER}"
}

示例 DataProtection 部分:

"DataProtection": {
  "BlobUri": "https://contoso.blob.core.windows.net/data-protection/keys.xml",
  "KeyIdentifier": "https://contoso.vault.azure.net/keys/data-protection"
}

注意

前面的示例中的密钥标识符是 无版本。 标识符末尾没有 GUID 密钥版本。 如果选择为密钥配置自动密钥轮换,这一点尤其重要。 有关详细信息,请参阅 在 Azure Key Vault 中配置加密密钥自动轮换:密钥轮换策略

Program 文件中进行以下更改:

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    options =>
    {
+       var config = builder.Configuration.GetSection("DistributedTokenCache");

-       options.DisableL1Cache = false;
+       options.DisableL1Cache = config.GetValue<bool>("DisableL1Cache");

-       options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024;
+       options.L1CacheOptions.SizeLimit = config.GetValue<long>("L1CacheSizeLimit");

-       options.Encrypt = true;
+       options.Encrypt = config.GetValue<bool>("Encrypt");

-       options.SlidingExpiration = TimeSpan.FromHours(1);
+       options.SlidingExpiration = 
+           TimeSpan.FromHours(config.GetValue<int>("SlidingExpirationInHours"));
    });

- builder.Services.AddDataProtection()
-     .SetApplicationName("BlazorWebAppEntra")
-     .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential)
-     .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential);

Program 文件中服务配置的地方添加以下代码:

var config = builder.Configuration.GetSection("DataProtection");

builder.Services.AddDataProtection()
    .SetApplicationName("BlazorWebAppEntra")
    .PersistKeysToAzureBlobStorage(
        new Uri(config.GetValue<string>("BlobUri") ??
        throw new Exception("Missing Blob URI")),
        credential)
    .ProtectKeysWithAzureKeyVault(
        new Uri(config.GetValue<string>("KeyIdentifier") ?? 
        throw new Exception("Missing Key Identifier")), 
        credential);

有关使用共享数据保护密钥环和密钥存储提供程序的详细信息,请参阅以下资源:

YARP 转发器目标前缀

Blazor Web App 服务器项目的 YARP 转发器中,将用户的访问令牌附加到 MinimalApiJwt 的 Web API 调用上,并指定目标前缀为 https://weatherapi。 此值与传递到 Aspire.AppHost 项目的 Program 文件中的 AddProject 的项目名称一致。

服务器项目中的 Blazor Web App 转发器(BlazorWebAppEntra):

app.MapForwarder("/weather-forecast", "https://weatherapi", transformBuilder =>
{
    ...
}).RequireAuthorization();

请在 Aspire App Host 项目 (Aspire.AppHost) 的 Program 文件中匹配项目名称:

var weatherApi = builder.AddProject<Projects.MinimalApiJwt>("weatherapi");

在将 Blazor Web App 部署到生产环境时,无需更改 YARP 转发器的目标前缀。 Microsoft Identity Web 下游 API 包使用通过配置传递的基 URI 从 ServerWeatherForecaster 进行 Web API 调用,而不是使用 YARP 转发器的目标前缀。 在生产环境中,YARP 转发器仅转换请求,并添加用户的访问令牌。

在注销时重定向到主页

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>

天气数据安全性

有关该应用程序如何保护其天气数据的更多信息,请参阅交互式自动渲染中Blazor Web App数据的保护方式

故障排除

日志记录

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

若要启用用于Blazor WebAssembly身份验证的调试或跟踪日志记录,请参阅中的Blazor部分,并将文章版本选择器设置为 .NET 7 或更高版本的 ASP.NET Core。

常见错误

  • 使用 Microsoft Entra 外部 ID 注销期间,调试器因异常而中断

    以下异常导致 Visual Studio 调试器在使用 Microsoft Entra 外部 ID 登出时停止:

    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) 配置错误

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

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

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

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

    • 分析应用程序日志。

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

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

    上述论坛并非 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 库

从正确的项目启动解决方案

Blazor Web App:

  • 对于前端后端模式示例之一,请从 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;
    }
}

其他资源