为 ASP.NET Core 服务器端 Blazor 应用提供保护

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本

重要

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

对于当前版本,请参阅此文的 .NET 8 版本

本文介绍如何将服务器端 Blazor 应用作为 ASP.NET Core 应用程序来提供保护。

服务器端 Blazor 应用采用与 ASP.NET Core 应用相同方式的安全配置。 有关详细信息,请参阅 ASP.NET Core 安全性主题下的文章。

身份验证上下文仅在应用启动时建立,即应用首次连接到 WebSocket 时。 线路的生存期内会保留身份验证上下文。 应用会定期(每隔 30 分钟)重新验证用户的身份验证状态。

如果应用必须捕获自定义服务的用户或对用户的更新做出响应,请参阅服务器端 ASP.NET Core Blazor 其他安全方案

Blazor 不同于传统服务器渲染的 Web 应用,后者在每次页面导航时都使用 Cookie 发出新的 HTTP 请求。 在导航事件期间检查身份验证。 但不涉及 Cookie。 仅当向服务器发出 HTTP 请求时,才会发送 Cookie,用户在 Blazor 应用中导航时不会发生这种情况。 在导航过程中,会在线路中 Blazor 检查用户的身份验证状态,你可以使用重新 AuthenticationStateProvider验证 ](#additional-authentication-state-providers)随时在服务器上更新该状态。

重要

不建议在导航期间执行自定义 NavigationManager 以实现身份验证。 如果应用必须在导航期间执行自定义身份验证状态逻辑,请使用自定义 AuthenticationStateProvider

注意

本文中的代码示例采用在 .NET 6 或更高版本的 ASP.NET Core 中支持的可为空的引用类型 (NRT) 和 .NET 编译器 Null 状态静态分析。 面向 ASP.NET Core 5.0 或更早版本时,请从文章示例中删除 null 类型指定 (?)。

敏感数据和凭据的服务器端安全性

在测试/暂存和生产环境中,服务器端 Blazor 代码和 Web API 应使用安全身份验证流,以避免在项目代码或配置文件中维护凭据。 在本地开发测试之外,我们建议避免使用环境变量来存储敏感数据,因为环境变量不是最安全的方法。 对于本地开发测试, 建议使用机密管理器工具 来保护敏感数据。 有关更多信息,请参阅以下资源:

对于客户端和服务器端本地开发和测试,请使用 机密管理器工具来 保护敏感凭据。

项目模板

按照适用于 ASP.NET Core Blazor 的工具中的指南创建新的服务器端 Blazor 应用。

选择服务器端应用模板并配置项目后,在“身份验证类型”下选择应用的身份验证

  • (默认):无身份验证。
  • 个人帐户:使用 ASP.NET Core Identity 将用户帐户存储在应用中。
  • (默认):无身份验证。
  • 个人帐户:使用 ASP.NET Core Identity 将用户帐户存储在应用中。
  • Microsoft identity 平台:有关更多信息,请参阅 ASP.NET Core Blazor 身份验证和授权
  • Windows:使用 Windows 身份验证。

BlazorIdentity UI(个人帐户)

选择个人帐户的身份验证选项时,Blazor 支持生成基于 Blazor 的完整 Identity UI。

Blazor Web App 模板为 SQL Server 数据库搭建 Identity 代码框架。 命令行版本使用 SQLite,并包含 Identity 的 SQLite 数据库。

模板:

  • 支持经过身份验证的用户的交互式服务器端呈现(交互式 SSR)和客户端呈现 (CSR) 方案。
  • 添加 IdentityRazor 组件和相关逻辑,用于例行身份验证任务,例如让用户登录和注销。这些 Identity 组件还支持高级 Identity 功能,例如使用第三方应用进行帐户确认和密码恢复多重身份验证。 请注意,Identity 组件本身不支持交互性。
  • 添加 Identity 相关的包和依赖项。
  • 引用 _Imports.razor 中的 Identity 包。
  • 创建自定义用户 Identity 类(ApplicationUser)。
  • 创建和注册 EF Core 数据库上下文(ApplicationDbContext)。
  • 配置内置 Identity 终结点的路由。
  • 包含 Identity 验证和业务逻辑。

若要检查 Blazor 框架的 Identity 组件,请在 Blazor Web App 项目模板(引用源)中的 Account 文件夹PagesShared 文件夹中访问它们。

选择交互式 WebAssembly 或“交互式自动”呈现模式时,服务器将处理所有身份验证和授权请求,Identity 组件将在 Blazor Web App 的主项目中的服务器上静态呈现。

该框架在服务器和客户端 (.Client) 项目中提供自定义 AuthenticationStateProvider,用于将用户的身份验证状态流向浏览器。 服务器项目会调用 AddAuthenticationStateSerialization,而客户端项目会调用 AddAuthenticationStateDeserialization。 在服务器上而不是在客户端上进行身份验证,可以让应用在预呈现期间和初始化 .NET WebAssembly 运行时之前访问身份验证状态。 自定义 AuthenticationStateProvider 实现使用 永久性组件状态服务(PersistentComponentState)将身份验证状态序列化为 HTML 注释,然后重新从 WebAssembly 读取,以创建新的 AuthenticationState 实例。 有关详细信息,请参阅管理 Blazor Web App 中的身份验证状态部分。

仅适用于交互式服务器解决方案,IdentityRevalidatingAuthenticationStateProvider(引用源) 是服务器端 AuthenticationStateProvider,每 30 分钟重新评估连接交互式线路时连接的用户的安全戳。

选择交互式 WebAssembly 或“交互式自动”呈现模式时,服务器将处理所有身份验证和授权请求,Identity 组件将在 Blazor Web App 的主项目中的服务器上静态呈现。 项目模板包含 .Client 项目中的 PersistentAuthenticationStateProvider 类(引用源),用于在服务器和浏览器之间同步用户的身份验证状态。 该类是 AuthenticationStateProvider 的一个自定义实现。 提供程序使用永久性组件状态服务(PersistentComponentState)预呈现身份验证状态并将其保存到页面。

在 Blazor Web App 的主项目中,身份验证状态提供程序命名为 IdentityRevalidatingAuthenticationStateProvider(引用源)(仅限服务器交互解决方案)或 PersistingRevalidatingAuthenticationStateProvider(引用源)(WebAssembly 或自动交互解决方案)。

BlazorIdentity 取决于 DbContext 实例不由中心创建,这是有意的,因为 DbContext 足以让项目模板的 Identity 组件静态呈现,而无需支持交互性。

有关如何在为 Identity 组件强制实施静态 SSR 的同时将全局交互式呈现模式应用于非 Identity 组件的说明,请参阅 ASP.NET Core Blazor 呈现模式

有关保留预呈现状态的详细信息,请参阅《预呈现 ASP.NET 核心 Razor 组件》。

有关 BlazorIdentity UI 的详细信息以及通过社交网站集成外部登录的指南,请参阅 .NET 8 中 identity 的新增功能

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

管理 Blazor Web App 中的身份验证状态

本节适用于 Blazor Web App,其采用:

  • 个人帐户
  • 客户端呈现(CSR、基于 WebAssembly 的交互性)。

客户端身份验证状态提供程序仅在 Blazor 中使用,不与 ASP.NET Core 身份验证系统集成。 在预呈现期间,Blazor 尊重页面上定义的元数据,并使用 ASP.NET Core 身份验证系统,以确定用户是否已通过身份验证。 当用户从一个页面导航到另一个页面时,将使用客户端身份验证提供程序。 当用户刷新页面(重新加载整个页面)时,客户端身份验证状态提供程序不参与服务器上的身份验证决策。 由于服务器没有持久化用户的状态,因此客户端维护的任何身份验证状态都将丢失。

为了解决这个问题,最佳方法是在 ASP.NET Core 身份验证系统内执行身份验证。 客户端身份验证状态提供程序只负责反映用户的身份验证状态。 Blazor Web App 项目模板演示了如何使用身份验证状态提供程序实现这一点的示例,如下所示:

在服务器项目的 Program 文件中,调用 AddAuthenticationStateSerialization,它使用 持久组件状态服务(PersistentComponentState)序列化服务器端 AuthenticationStateProvider 返回的 AuthenticationState

builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents()
    .AddAuthenticationStateSerialization();

API 只会序列化服务器端的名称和角色声明,以便在浏览器中访问。 若要包括所有声明,请在服务器端调用中将 SerializeAllClaims 设置为 trueAddAuthenticationStateSerialization

builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents()
    .AddAuthenticationStateSerialization(
        options => options.SerializeAllClaims = true);

在客户端 (.Client) 项目的 Program 文件中,调用 AddAuthenticationStateDeserialization,这会添加一个 AuthenticationStateProvider,其中使用 AuthenticationStateData 持久组件状态服务 (PersistentComponentState) 从服务器反序列化 AuthenticationState。 服务器项目中应有对 AddAuthenticationStateSerialization 的相应调用。

builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthenticationStateDeserialization();

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

设置Identity的基架

有关如何将 Identity 架构到服务器端 Blazor 应用中的详细信息,请参阅在 ASP.NET Core 项目中设置 Identity 的基架

将 Identity 架构到服务器端 Blazor 应用中:

来自外部提供程序的额外声明和令牌

若要存储来自外部提供程序的其他声明,请参阅在 ASP.NET Core 中保留来自外部提供程序的其他声明和令牌

使用 Identity 服务器的 Linux 上的 Azure 应用服务

使用 Identity 服务器部署到 Linux 上的 Azure 应用服务时,显式指定颁发者。 有关详细信息,请参阅使用 Identity 保护 SPA 的 Web API 后端

为作用到组件的服务注入 AuthenticationStateProvider

请勿尝试在自定义范围内解析 AuthenticationStateProvider,因为这会导致创建未正确初始化的 AuthenticationStateProvider 的新实例。

要访问作用到组件的服务内的 AuthenticationStateProvider,请使用 @inject 指令[Inject] 属性注入 AuthenticationStateProvider,并将其作为参数传递给服务。 此方法可确保对每个用户应用实例使用正确且初始化的 AuthenticationStateProvider 实例。

ExampleService.cs:

public class ExampleService
{
    public async Task<string> ExampleMethod(AuthenticationStateProvider authStateProvider)
    {
        var authState = await authStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            return $"{user.Identity.Name} is authenticated.";
        }
        else
        {
            return "The user is NOT authenticated.";
        }
    }
}

将服务注册为已限定范围。 在服务器端 Blazor 应用中,范围内服务的生存期等于客户端连接线路的持续时间。

Program 文件中:

builder.Services.AddScoped<ExampleService>();

Startup.csStartup.ConfigureServices 中:

services.AddScoped<ExampleService>();

在下面的 InjectAuthStateProvider 组件中:

InjectAuthStateProvider.razor:

@page "/inject-auth-state-provider"
@inherits OwningComponentBase
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>Inject <code>AuthenticationStateProvider</code> Example</h1>

<p>@message</p>

@code {
    private string? message;
    private ExampleService? ExampleService { get; set; }

    protected override async Task OnInitializedAsync()
    {
        ExampleService = ScopedServices.GetRequiredService<ExampleService>();

        message = await ExampleService.ExampleMethod(AuthenticationStateProvider);
    }
}
@page "/inject-auth-state-provider"
@inject AuthenticationStateProvider AuthenticationStateProvider
@inherits OwningComponentBase

<h1>Inject <code>AuthenticationStateProvider</code> Example</h1>

<p>@message</p>

@code {
    private string? message;
    private ExampleService? ExampleService { get; set; }

    protected override async Task OnInitializedAsync()
    {
        ExampleService = ScopedServices.GetRequiredService<ExampleService>();

        message = await ExampleService.ExampleMethod(AuthenticationStateProvider);
    }
}

有关详细信息,请参阅 ASP.NET Core Blazor 依赖项注入OwningComponentBase 的相关指南。

使用自定义 AuthenticationStateProvider 预呈现时显示未经授权的内容

为了避免在使用自定义 AuthenticationStateProvider 预呈现时显示未经授权的内容(例如 AuthorizeView 组件中的内容),请采用以下方法之一

  • 禁用预呈现:通过在应用组件层次结构中的最高级别组件(不是根组件)处将 prerender 参数设置为 false 来指示呈现模式。

    注意

    不支持让根组件具有交互性(例如 App 组件)。 因此,App 组件无法直接禁用预呈现。

    对于基于 Blazor Web App 项目模板的应用,如果在 App 组件 (Components/App.razor) 中使用了 Routes 组件,通常会禁用预呈现:

    <Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />
    

    此外,请禁用 HeadOutlet 组件的预呈现:

    <HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />
    

    还可选择性地控制应用于 Routes 组件实例的呈现模式。 例如,请参阅 ASP.NET Core Blazor 呈现模式

  • 禁用预呈现:打开 _Host.cshtml 文件,并将组件标记帮助程序render-mode 属性更改为 Server

    <component type="typeof(App)" render-mode="Server" />
    
  • 在应用启动前对服务器上的用户进行身份验证:要采用此方法,应用必须使用基于 Identity 的登录页或视图响应用户的初始请求,并阻止任何对 Blazor 终结点的请求,直到其进行身份验证。 有关详细信息,请参阅通过授权保护的用户数据创建 ASP.NET Core 应用。 身份验证后,只有当用户真正未经授权查看内容时,才会显示预呈现 Razor 组件中的未授权内容。

用户状态管理

尽管名称中存在“state”一词,但 AuthenticationStateProvider 不适用于存储常规用户状态。 AuthenticationStateProvider 仅向应用指示用户的身份验证状态,无论用户是否已登录到应用,又是以何种身份进行登录的。

身份验证使用与 Razor Pages 和 MVC 应用相同的 ASP.NET Core Identity 身份验证。 针对 ASP.NET Core Identity 存储的用户状态流向 Blazor,而无需向应用添加其他代码。 按照 ASP.NET Core Identity 文章和教程中的指南执行操作,使 Identity 功能在应用的 Blazor 部分生效。

有关 ASP.NET Core Identity 之外的常规状态管理的指南,请参阅 ASP.NET Core Blazor 状态管理

其他身份验证状态提供程序

在服务器上管理身份验证状态的帮助派生的 AuthenticationStateProvider 另外两个类:

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

注销时的身份验证状态管理

服务器端 Blazor 在线路的生存期内保留用户身份验证状态,包括跨浏览器选项卡。 若要在用户在一个选项卡上注销时跨浏览器选项卡主动注销用户,必须使用一个简短 RevalidationInterval 来实现 RevalidatingServerAuthenticationStateProvider引用源)。

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

临时重定向 URL 有效期

本部分适用于 Blazor Web App。

使用 RazorComponentsServiceOptions.TemporaryRedirectionUrlValidityDuration 选项获取或设置由 Blazor 服务器端呈现所发出的临时重定向 URL 的 ASP.NET Core 数据保护有效性的期限。 这些只是暂时使用,因此生存期只需为客户端提供足够时间接收 URL 和开始导航到该 URL 即可。 但它仍应足够长,足以满足跨服务器的时钟偏斜。 默认值为 5 分钟。

在以下示例中,该值延长至 7 分钟:

builder.Services.AddRazorComponents(options => 
    options.TemporaryRedirectionUrlValidityDuration = 
        TimeSpan.FromMinutes(7));

其他资源