ASP.NET Core Blazor 身份验证和授权

注意

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

警告

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

重要

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

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

本文介绍 ASP.NET Core 对 Blazor 应用中的安全配置和管理的支持。

Blazor 使用现有的 ASP.NET Core 身份验证机制来确立用户的身份。 具体机制取决于在服务器端或客户端托管 Blazor 应用的方式。

服务器端和客户端运行的授权代码在 Blazor 应用中的安全方案存在差异。 对于在服务器上运行的授权代码,授权检查能够对应用和组件区域强制实施访问规则。 由于客户端代码执行可能会被篡改,因此无法信任在客户端上执行的授权代码必定强制实施访问规则或控制客户端内容的显示。

如果必须保证强制实施授权规则,请不要在客户端代码中实现授权检查。 构建一个仅依赖服务器端呈现 (SSR) 进行授权检查和规则强制实施的 Blazor Web App。

如果必须保证强制实施授权规则并保证数据和代码安全,请不要开发客户端应用。 构建 Blazor Server 应用。

Razor 页面授权约定 不适用于可 Razor 路由的组件。 如果不可路由Razor的组件嵌入到 Pages 应用的页面中Razor,页面的授权约定会间接影响Razor该组件以及页面内容的其余部分。

ASP.NET Core Identity 设计用于 HTTP 请求和响应通信的上下文中,通常不是 Blazor 应用客户端-服务器通信模型。 将 ASP.NET Core Identity 用于用户管理的 ASP.NET Core 应用应该使用 Razor Pages,而不是 Razor 相关的 UI 的 Identity 组件,例如用户注册、登录、注销和其他用户管理任务。 在多种场景下,构建直接处理 Razor 任务的 Identity 组件是可行的,但 Microsoft 不建议或不支持此操作。

SignInManager<TUser> 组件中不支持 ASP.NET Core 抽象,例如 UserManager<TUser> 和 Razor。 有关将 ASP.NET Core Identity 与 Blazor配合使用的详细信息,请参阅 将基架 ASP.NET Core Identity 添加到服务器端 Blazor 应用中

注意

本文中的代码示例采用了可为空的引用类型(NRT)和 .NET 编译器的空状态静态分析,这些功能在 .NET 6 或更高版本的 ASP.NET Core 中受支持。 针对 .NET 5 或更早版本时,请从本文中的示例中删除 null 类型指定(?)。

安全地维护敏感数据和凭据

请勿在客户端代码中存储应用机密、连接字符串、凭据、密码、个人标识号(PIN)、专用 .NET/C# 代码或私钥/令牌,这 始终不安全。 Blazor客户端代码应通过你控制的安全的 Web API 访问安全服务和数据库。

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

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

Microsoft Azure 服务的托管标识

对于Microsoft Azure 服务,我们建议使用 托管标识。 托管标识可安全地向 Azure 服务进行身份验证,而无需在应用代码中存储凭据。 有关更多信息,请参阅以下资源:

防伪支持

Blazor 模板:

AntiforgeryToken 组件将防伪令牌呈现为隐藏字段,此组件会自动添加到窗体 (EditForm) 实例中。 有关详细信息,请参阅 ASP.NET 核心 Blazor 表单概述

AntiforgeryStateProvider 服务提供对与当前会话关联的防伪令牌的访问权限。 注入服务并调用其 GetAntiforgeryToken() 方法以获取当前的 AntiforgeryRequestToken。 有关详细信息,请参阅 从 ASP.NET Core Blazor 应用调用 Web API

Blazor 将请求令牌存储在组件状态中,这可以保证防伪令牌可供交互式组件使用,即便它们无权访问请求。

注意

只有在将表单数据以 application/x-www-form-urlencodedmultipart/form-data 的编码提交到服务器时,才要求text/plain,因为这些是唯一有效的表单编码方式

有关更多信息,请参阅以下资源:

服务器端 Blazor 身份验证

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

身份验证上下文仅在应用启动时建立,即当应用首次 通过 SignalR 与客户端的连接连接到 WebSocket 时。 身份验证可以基于 cookie 或某个其他持有者令牌,但身份验证通过 SignalR 中心和完全在线路中进行管理。 在电路的生命周期内会保留身份验证上下文。 应用会定期(每隔 30 分钟)重新验证用户的身份验证状态。

如果应用必须捕获用户以获取自定义服务或响应用户更新,请参阅 ASP.NET 核心服务器端和其他 Blazor Web App 安全方案

Blazor 不同于传统的服务器呈现的 Web 应用,这些应用在每个页面导航上使用 Cookie 发出新的 HTTP 请求。 在导航事件期间检查身份验证。 但不涉及 Cookie。 仅当向服务器发出 HTTP 请求时,才会发送 Cookie,用户在 Blazor 应用中导航时不会发生这种情况。 在导航过程中,会在电路Blazor中检查用户的身份验证状态,你可以随时在服务器上使用RevalidatingAuthenticationStateProvider抽象来更新该状态。

重要

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

注意

本文中的代码示例采用了可为空的引用类型(NRT)和 .NET 编译器的空状态静态分析,这些功能在 .NET 6 或更高版本的 ASP.NET Core 中受支持。 针对 .NET 5 或更早版本时,请从本文中的示例中删除 null 类型指定(?)。

内置或自定义的 AuthenticationStateProvider 服务可从 ASP.NET Core 的 HttpContext.User 获取身份验证状态数据。 身份验证状态就是这样与现有 ASP.NET Core 身份验证机制集成。

有关服务器端身份验证的详细信息,请参阅 ASP.NET 核心 Blazor 身份验证和授权

共享状态

服务器端 Blazor 应用位于服务器内存中,多个应用会话托管在同一进程中。 对于每个应用会话,Blazor 根据其自身的依赖项注入容器范围启动线路,因此,作用域服务对于每个 Blazor 会话是唯一的。

警告

我们不建议同一服务器上的应用共享使用单一实例服务的状态,除非采取了极其谨慎的措施,因为这可能会带来安全漏洞,如跨线路泄露用户状态。

如果有状态的单一实例服务是专门为 Blazor 应用设计的,则可以在这些应用中使用这些服务。 例如,使用单例模式的内存缓存是可以接受的,因为内存缓存需要一个键来访问给定条目。 假设用户无法控制随缓存一起使用的缓存键,则存储在缓存中的状态不会跨线路泄漏。

有关状态管理的一般指南,请参阅 ASP.NET 核心 Blazor 状态管理

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

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

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

项目模板

按照 Blazor中的指南创建新的服务器端Blazor应用程序。

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

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

Blazor Identity UI(个人帐户)

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

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

模板:

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

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

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

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

仅适用于交互式服务器解决方案,(引用源)是一个服务器端组件,每当交互式电路连接时,每 30 分钟重新验证连接用户的安全标记。

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

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

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

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

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

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET 核心源代码的版本标记(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,该调用使用AuthenticationState来序列化服务器端AuthenticationStateProvider返回的PersistentComponentState):

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

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

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

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

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

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET 核心源代码的版本标记(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,请将 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.ConfigureServicesStartup.cs 中:

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

有关详细信息,请参阅 OwningComponentBase中的指南Blazor。

在使用自定义 AuthenticationStateProvider 进行预呈现时,显示了未经授权的内容。

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

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

    注意

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

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

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

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

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

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

  • 禁用预呈现:打开_Host.cshtml文件,并将render-mode属性更改为Server

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

用户状态管理

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

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

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

其他安全抽象

另外两个抽象参与管理身份验证状态:

注意

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

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

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

注意

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

临时重定向 URL 有效期

本部分适用于 Blazor Web Apps。

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

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

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

客户端 Blazor 身份验证

在客户端 Blazor 应用中,可以绕过客户端身份验证检查,因为用户可以修改所有客户端代码。 所有客户端应用程序技术都是如此,其中包括 JavaScript SPA 框架和任何操作系统的本机应用程序。

添加以下内容:

若要处理身份验证,请使用内置或自定义 AuthenticationStateProvider 服务。

有关客户端身份验证的详细信息,请参阅 安全 ASP.NET 核心 Blazor WebAssembly

使用交互式自动呈现保护 Blazor Web App 中的数据

Blazor Web App如果对组件或指定交互式自动呈现模式的整个应用采用服务器端呈现(SSR)和客户端呈现(CSR),则会在两个位置应用访问组件和数据授权。 组件通过组件定义文件中@attribute [Authorize]的授权属性在服务器上呈现时,限制对自身(及其获取的任何数据)的访问。 客户端渲染组件时,通过从客户端调用的服务器 Web API 端点限制对数据的访问。 在这两个位置确保数据访问安全时必须谨慎,以防止数据访问不当。

请考虑以下方案,其中安全天气数据由组件显示。 可以使用示例 GitHub 存储库(BlazorWebAppEntra)中的BlazorWebAppOidc/BlazorWebAppOidcBffBlazor示例(.NET 8 或更高版本)来评估和测试dotnet/blazor-samples以下一些方法的演示。

客户端项目维护一个 WeatherForecast 类来保存天气数据:

public sealed class WeatherForecast(DateOnly date, int temperatureC, string summary)
{
    public DateOnly Date { get; set; } = date;
    public int TemperatureC { get; set; } = temperatureC;
    public string? Summary { get; set; } = summary;
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

客户端项目的 IWeatherForecaster 接口定义用于 GetWeatherForecastAsync 获取天气数据的方法:

public interface IWeatherForecaster
{
    Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync();
}

客户端项目的 ClientWeatherForecaster 服务实现 IWeatherForecaster。 该方法在服务器项目的 GetWeatherForecastAsync 终结点调用 Web API 以获取天气数据:

internal sealed class ClientWeatherForecaster(HttpClient httpClient) 
    : IWeatherForecaster
{
    public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync() =>
        await httpClient.GetFromJsonAsync<WeatherForecast[]>("/weather-forecast") ??
            throw new IOException("No weather forecast!");
}

客户端项目维护一个 Weather 组件,该组件:

@page "/weather"
@using Microsoft.AspNetCore.Authorization
@using BlazorWebAppEntra.Client.Weather
@attribute [Authorize]
@inject IWeatherForecaster WeatherForecaster

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data.</p>

@if (Forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th aria-label="Temperature in Celsius">Temp. (C)</th>
                <th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in Forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    [SupplyParameterFromPersistentComponentState]
    public IEnumerable<WeatherForecast>? Forecasts { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Forecasts ??= await WeatherForecaster.GetWeatherForecastAsync();
    }
}
@page "/weather"
@using Microsoft.AspNetCore.Authorization
@using BlazorWebAppEntra.Client.Weather
@attribute [Authorize]
@implements IDisposable
@inject PersistentComponentState ApplicationState
@inject IWeatherForecaster WeatherForecaster

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th aria-label="Temperature in Celsius">Temp. (C)</th>
                <th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private IEnumerable<WeatherForecast>? forecasts;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<IEnumerable<WeatherForecast>>(
            nameof(forecasts), out var restoredData))
        {
            forecasts = await WeatherForecaster.GetWeatherForecastAsync();
        }
        else
        {
            forecasts = restoredData!;
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson(nameof(forecasts), forecasts);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose() => persistingSubscription.Dispose();
}

服务器项目实现 IWeatherForecasterServerWeatherForecaster,它通过其 GetWeatherForecastAsync 方法生成和返回天气数据:

internal sealed class ServerWeatherForecaster() : IWeatherForecaster
{
    public readonly string[] summaries =
    [
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", 
        "Sweltering", "Scorching"
    ];

    public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync()
    {
        // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(500);

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

如果应用必须调用外部 Web API 才能获取天气数据,可以注入 HTTP 客户端(HttpClient)来请求数据:

internal sealed class ServerWeatherForecaster(HttpClient httpClient, 
    IHttpContextAccessor httpContextAccessor) : IWeatherForecaster
{
    public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync()
    {
        var httpContext = httpContextAccessor.HttpContext ??
            throw new InvalidOperationException("No HttpContext!");
        var accessToken = await httpContext.GetTokenAsync("access_token") ??
            throw new InvalidOperationException("No access_token was saved");
        using var request = 
            new HttpRequestMessage(HttpMethod.Get, "/weather-forecast");
        request.Headers.Authorization = new("Bearer", accessToken);
        using var response = await httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

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

在另一种途径中,可以将 HTTP 客户端工厂 (IHttpClientFactory) 注入 ServerWeatherForecaster 中,并使用具有令牌处理程序的命名 HTTP 客户端调用外部网络API。 有关详细信息,请参阅 从 ASP.NET Core Blazor 应用调用 Web API

如果应用将Microsoft标识平台用于Microsoft Identity Entra ID Microsoft Web 包(请参阅从 ASP.NET Core Blazor 应用调用 Web API),则以下ServerWeatherForecaster演示了进行外部 Web API 调用。 访问令牌会自动附加到请求。

internal sealed class ServerWeatherForecaster(IDownstreamApi downstreamApi) : IWeatherForecaster
{
    public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync()
    {
        using var response = await downstreamApi.CallApiForUserAsync("DownstreamApi",
            options =>
            {
                options.RelativePath = "/weather-forecast";
            });

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

无论获取数据采用哪种方法 ServerWeatherForecaster ,服务器项目都会为客户端天气数据调用维护安全的 Web API 终结点。 此终结点会导致在服务器上执行 ServerWeatherForecaster.GetWeatherForecastAsync 调用。

app.MapGet("/weather-forecast", (
    [FromServices] IWeatherForecaster WeatherForecaster) =>
{
    return WeatherForecaster.GetWeatherForecastAsync();
}).RequireAuthorization();

使用上述方法,有两个系统可以向用户提供安全的天气数据:

  • Weather 组件在服务器上呈现时,ServerWeatherForecaster 服务的 GetWeatherForecastAsync 方法将直接用于获取天气数据。 数据的安全性由组件 [Authorize] 的属性强制执行。 总之,天气数据的安全性由组件强制执行。
  • Weather 组件在客户端上呈现时,ClientWeatherForecaster 服务将被用于向应用 /weather-forecast 扩展方法的安全 RequireAuthorization 终结点发出 Web API 调用。 如果用户有权访问天气数据,终结点将使用 ServerWeatherForecaster 服务调用 GetWeatherForecastAsync。 数据将返回到客户端。 总之,天气数据的安全性由服务器应用的 Web API 终结点强制执行。

当 Web API 的安全要求与组件的安全要求匹配时,上述方法非常有效。 例如,同一授权策略可以同时应用于 Web API 终结点和组件。

复杂的方案需要额外的规划和实现。 例如,具有不同访问权限的多个调用方的服务器 Web API 需要更复杂的授权策略、一个或多个附加策略或具有不同访问要求的其他终结点。

在将安全性集成到采用交互式自动呈现的应用程序中时,请注意,为服务器的 Web API 端点所实施的安全措施,并不能保护服务器上呈现组件时所使用的服务实现,这一实现通过服务访问数据。 仔细权衡在 SSR 期间访问服务器上的数据与在 CSR 期间访问客户端 Web API 请求上的数据之间的差异。 以战略方式应用安全性,以避免对数据进行不当访问。

GitHub 存储库 () (Blazor如何下载)示例中的dotnet/blazor-samples示例,演示了本节中所述的方法:

  • BlazorWebAppOidc
  • BlazorWebAppOidcBff
  • BlazorWebAppEntra
  • BlazorWebAppEntraBff

AuthenticationStateProvider 服务

AuthenticationStateProviderAuthorizeView 组件和级联身份验证服务用于获取用户的身份验证状态的基础服务。

AuthenticationStateProviderAuthorizeView 组件和 CascadingAuthenticationState 组件用于获取用户身份验证状态的基础服务。

通常不直接使用 AuthenticationStateProvider。 请使用本文后面介绍的AuthorizeView组件Task<AuthenticationState>方法。 直接使用 AuthenticationStateProvider 的主要缺点是,如果基础身份验证状态数据发生更改,不会自动通知组件。

若要实现自定义 AuthenticationStateProvider,请参阅 ASP.NET 核心 Blazor 身份验证状态,其中包括有关实现用户身份验证状态更改通知的指导。

获取用户的声明主体数据

AuthenticationStateProvider 服务可以提供当前用户的 ClaimsPrincipal 数据,如以下示例所示。

ClaimsPrincipalData.razor:

@page "/claims-principal-data"
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>ClaimsPrincipal Data</h1>

<button @onclick="GetClaimsPrincipalData">Get ClaimsPrincipal Data</button>

<p>@authMessage</p>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}

<p>@surname</p>

@code {
    private string? authMessage;
    private string? surname;
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    private async Task GetClaimsPrincipalData()
    {
        var authState = await AuthenticationStateProvider
            .GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            authMessage = $"{user.Identity.Name} is authenticated.";
            claims = user.Claims;
            surname = user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value;
        }
        else
        {
            authMessage = "The user is NOT authenticated.";
        }
    }
}

在上面的示例中:

@page "/claims-principal-data"
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>ClaimsPrincipal Data</h1>

<button @onclick="GetClaimsPrincipalData">Get ClaimsPrincipal Data</button>

<p>@authMessage</p>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}

<p>@surname</p>

@code {
    private string? authMessage;
    private string? surname;
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    private async Task GetClaimsPrincipalData()
    {
        var authState = await AuthenticationStateProvider
            .GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            authMessage = $"{user.Identity.Name} is authenticated.";
            claims = user.Claims;
            surname = user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value;
        }
        else
        {
            authMessage = "The user is NOT authenticated.";
        }
    }
}

如果 user.Identity.IsAuthenticatedtrue,且由于用户是 ClaimsPrincipal,则可以枚举声明并评估角色成员身份。

有关依赖注入(DI)和服务的详细信息,请参阅 ASP.NET Core Blazor 依赖注入ASP.NET Core 中的依赖注入。 有关如何实现自定义 AuthenticationStateProvider的信息,请参阅 ASP.NET 核心 Blazor 身份验证状态

公开身份验证状态作为级联参数

如果过程逻辑需要身份验证状态数据(如在执行用户触发的操作时),请通过定义类型为 Task<AuthenticationState>来获取身份验证状态数据,如以下示例所示。

CascadeAuthState.razor:

@page "/cascade-auth-state"

<h1>Cascade Auth State</h1>

<p>@authMessage</p>

@code {
    private string authMessage = "The user is NOT authenticated.";

    [CascadingParameter]
    private Task<AuthenticationState>? authenticationState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (authenticationState is not null)
        {
            var authState = await authenticationState;
            var user = authState?.User;

            if (user?.Identity is not null && user.Identity.IsAuthenticated)
            {
                authMessage = $"{user.Identity.Name} is authenticated.";
            }
        }
    }
}
@page "/cascade-auth-state"

<h1>Cascade Auth State</h1>

<p>@authMessage</p>

@code {
    private string authMessage = "The user is NOT authenticated.";

    [CascadingParameter]
    private Task<AuthenticationState>? authenticationState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (authenticationState is not null)
        {
            var authState = await authenticationState;
            var user = authState?.User;

            if (user?.Identity is not null && user.Identity.IsAuthenticated)
            {
                authMessage = $"{user.Identity.Name} is authenticated.";
            }
        }
    }
}

如果 user.Identity.IsAuthenticated 等于 true,则可以枚举声明并评估角色的成员资格。

使用 Task< 和级联身份验证状态服务设置 AuthenticationState>AuthorizeRouteView

从启用了身份验证的某个 Blazor 项目模板创建 Blazor 应用时,该应用包含 AuthorizeRouteView 和对 AddCascadingAuthenticationState 的调用,如下例所示。 客户端 Blazor 应用还包括所需的服务注册。 在“使用 Router 组件自定义未经授权内容”部分中提供了其他信息。

<Router ...>
    <Found ...>
        <AuthorizeRouteView RouteData="routeData" 
            DefaultLayout="typeof(Layout.MainLayout)" />
        ...
    </Found>
</Router>

Program文件中,注册层级式身份验证状态服务:

builder.Services.AddCascadingAuthenticationState();

使用 Task<AuthenticationState 组件设置>AuthorizeRouteViewCascadingAuthenticationState

从启用了身份验证的某个 Blazor 项目模板创建 Blazor 应用时,该应用包含 AuthorizeRouteViewCascadingAuthenticationState 组件,如下例所示。 客户端 Blazor 应用还包括所需的服务注册。 在“使用 Router 组件自定义未经授权内容”部分中提供了其他信息。

<CascadingAuthenticationState>
    <Router ...>
        <Found ...>
            <AuthorizeRouteView RouteData="routeData" 
                DefaultLayout="typeof(MainLayout)" />
            ...
        </Found>
    </Router>
</CascadingAuthenticationState>

注意

对于 .NET 5.0.1 和任何其他 5.x 版本,Router 组件包含的参数 PreferExactMatches 被设置为 @true。 有关详细信息,请参阅 从 ASP.NET Core 3.1 迁移到 .NET 5

在客户端 Blazor 应用中,将授权服务添加到 Program 文件:

builder.Services.AddAuthorizationCore();

在客户端 Blazor 应用中,将选项和授权服务添加到 Program 文件:

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

在服务器端 Blazor 应用中,已有选项和授权服务,因此无需执行进一步的步骤。

授权

对用户进行身份验证后,应用授权规则来控制用户可以执行的操作。

通常根据以下几点确定是授权访问还是拒绝访问:

  • 已对用户进行身份验证(已登录)。
  • 用户属于某个角色
  • 用户具有声明
  • 满足策略要求

上述所有概念都与 ASP.NET Core MVC 或 Razor Pages 应用中的概念相同。 有关 ASP.NET Core 安全性的详细信息,请参阅 ASP.NET Core Security 和 Identity以下文章。

AuthorizeView 组件

AuthorizeView 组件根据用户是否获得授权来选择性地显示 UI 内容。 如果只需要为用户显示数据,而不需要在过程逻辑中使用用户的标识,那么此方法很有用

该组件公开了一个 context 类型的 AuthenticationState 变量(@context 语法中的 Razor),可以使用该变量来访问有关已登录用户的信息:

<AuthorizeView>
    <p>Hello, @context.User.Identity?.Name!</p>
</AuthorizeView>

此外,如果用户未通过 AuthorizedNotAuthorized 参数的组合获得授权,你还可提供不同的内容以供显示:

<AuthorizeView>
    <Authorized>
        <p>Hello, @context.User.Identity?.Name!</p>
        <p><button @onclick="HandleClick">Authorized Only Button</button></p>
    </Authorized>
    <NotAuthorized>
        <p>You're not authorized.</p>
    </NotAuthorized>
</AuthorizeView>

@code {
    private void HandleClick() { ... }
}

尽管组件 AuthorizeView 基于用户的授权状态控制元素的可见性,但它不会对事件处理程序本身强制实施安全性。 在前面的示例中,该方法 HandleClick 仅与授权用户可见的按钮相关联,但不会阻止从其他地方调用此方法。 若要确保方法级安全性,在处理程序本身或相关 API 中实现其他授权逻辑。

当静态服务器端呈现(静态 SSR)期间服务器端授权失败时,Razor 的 Blazor Web App 组件永远不会显示 <NotAuthorized> 内容。 服务器端 ASP.NET 核心管道处理服务器上的授权。 使用服务器端技术来处理未经授权的请求。 有关详细信息,请参阅 ASP.NET 核心 Blazor 呈现模式

警告

AuthorizeView 关联的客户端标记和方法仅在客户端 应用中的 中受到查看和执行的保护。 为了保护客户端 Blazor 中的授权内容和安全方法,内容通常由对服务器 API 的安全授权 Web API 调用提供,并且永远不会存储在应用中。 有关详细信息,请参阅 从 ASP.NET Core Blazor 应用调用 Web API ,并 ASP.NET Core Blazor WebAssembly 其他安全方案

AuthorizedNotAuthorized 的内容可以包括任意项,如其他交互式组件。

授权条件(如控制 UI 选项或访问权限的角色或策略)包含在 授权 部分中。

如果未指定授权条件,则 AuthorizeView 使用默认策略:

  • 将经过身份验证(已登录)的用户视为已授权。
  • 将未经过身份验证(已注销)的用户视为未授权。

组件AuthorizeView()可用于NavMenuShared/NavMenu.razor显示NavLink组件NavLink),但请注意,此方法仅从呈现的输出中删除列表项。 它不会阻止用户导航到该组件。 在目标组件中单独实现授权。

基于角色和基于策略的授权

AuthorizeView 组件支持基于角色或基于策略的授权 。

对于基于角色的授权,请使用 Roles 参数。 在以下示例中,用户必须对 AdminSuperuser 角色具有角色声明:

<AuthorizeView Roles="Admin, Superuser">
    <p>You have an 'Admin' or 'Superuser' role claim.</p>
</AuthorizeView>

若要要求用户同时具有 AdminSuperuser 角色声明,请嵌套 AuthorizeView 组件:

<AuthorizeView Roles="Admin">
    <p>User: @context.User</p>
    <p>You have the 'Admin' role claim.</p>
    <AuthorizeView Roles="Superuser" Context="innerContext">
        <p>User: @innerContext.User</p>
        <p>You have both 'Admin' and 'Superuser' role claims.</p>
    </AuthorizeView>
</AuthorizeView>

前面的代码为内部 Context 组件建立一个 AuthorizeView,以防止 AuthenticationState 上下文冲突。 在外部 AuthenticationState 中,AuthorizeView 上下文是通过用于访问上下文 (@context.User) 的标准方法访问的。 该上下文在内部 AuthorizeView 中使用命名的 innerContext 上下文 (@innerContext.User) 进行访问。

有关详细信息,包括配置指南,请参阅 ASP.NET Core 中基于角色的授权

对于基于策略的授权,请使用 Policy 具有单个策略名称的参数:

<AuthorizeView Policy="Over21">
    <p>You satisfy the 'Over21' policy.</p>
</AuthorizeView>

若要处理用户应满足多个策略之一的情况,请创建可确认用户满足其他策略的策略。

若要处理用户必须同时满足多个策略的情况,请采用以下任一方法:

  • AuthorizeView 创建一个策略,用于确认用户是否满足其他几个策略。

  • 将策略嵌套在多个 AuthorizeView 组件中:

    <AuthorizeView Policy="Over21">
        <AuthorizeView Policy="LivesInCalifornia">
            <p>You satisfy the 'Over21' and 'LivesInCalifornia' policies.</p>
        </AuthorizeView>
    </AuthorizeView>
    

声明式授权是基于策略授权的一种特例。 例如,可以定义一个要求用户具有特定声明的策略。 有关详细信息,请参阅 ASP.NET Core 中基于策略的授权

必须同时满足 RolesPolicy 这两个条件,授权才会成功。 也就是说,用户必须至少属于指定角色之一 ,并 满足策略定义的要求。

如果 RolesPolicy 均未指定,则 AuthorizeView 使用默认策略:

  • 将经过身份验证(已登录)的用户视为已授权。
  • 将未经过身份验证(已注销)的用户视为未授权。

由于 .NET 字符串比较区分大小写,因此匹配的角色和策略名称也区分大小写。 例如,Admin(大写 A)不被视为与 admin(小写 a)相同的角色。

Pascal 大小写通常用于角色和策略名称(例如 BillingAdministrator),但使用 Pascal 大小写并非严格要求。 允许不同的包装方案,如骆驼元素、烤肉元素和蛇元素。 在角色和策略名称中使用空格也是不常见的,但框架允许使用。 例如,billing administrator 在 .NET 应用中是一种不常见的角色或策略名称格式,但却是一种有效的角色或策略名称。

异步身份验证期间显示的内容

通过 Blazor,可通过异步方式确定身份验证状态。 此方法的主要应用场景是客户端 Blazor 应用向外部终结点发出身份验证请求。

正在进行身份验证时,AuthorizeView 不显示任何内容。 若要在进行身份验证时显示内容,请将内容分配给 Authorizing 参数:

<AuthorizeView>
    <Authorized>
        <p>Hello, @context.User.Identity?.Name!</p>
    </Authorized>
    <Authorizing>
        <p>You can only see this content while authentication is in progress.</p>
    </Authorizing>
</AuthorizeView>

此方法通常不适用于服务器端 Blazor 应用。 身份验证状态一经确立,服务器端 Blazor 应用便会立即获知身份验证状态。 Authorizing 内容可以在应用的 AuthorizeView 组件中提供,但此内容从不显示。

[Authorize] 属性

属性在 组件中可用。

@page "/"
@attribute [Authorize]

You can only see this if you're signed in.

重要

请仅在通过 [Authorize] 路由器到达的 @page 组件上使用 Blazor。 授权仅作为路由的一个方面执行,而不是作为页面中呈现的子组件来执行。 若要授权在页面中显示特定部分,请改用 AuthorizeView

[Authorize] 属性 还支持基于角色或基于策略的授权。 对于基于角色的授权,请使用 Roles 参数:

@page "/"
@attribute [Authorize(Roles = "Admin, Superuser")]

<p>You can only see this if you're in the 'Admin' or 'Superuser' role.</p>

对于基于策略的授权,请使用 Policy 参数:

@page "/"
@attribute [Authorize(Policy = "Over21")]

<p>You can only see this if you satisfy the 'Over21' policy.</p>

如果 RolesPolicy 均未指定,则 [Authorize] 使用默认策略:

  • 将经过身份验证(已登录)的用户视为已授权。
  • 将未经过身份验证(已注销)的用户视为未授权。

如果用户未获得授权,并且应用未 使用 Router 组件自定义未经授权的内容,框架会自动显示以下回退消息:

Not authorized.

资源授权

若要授权用户访问资源,请将请求的路由数据传递到 ResourceAuthorizeRouteView 参数。

在请求的路由的 Router.Found 内容中:

<AuthorizeRouteView Resource="routeData" RouteData="routeData" 
    DefaultLayout="typeof(MainLayout)" />

有关如何在过程逻辑中传递和使用授权状态数据的详细信息,请参阅“ 公开身份验证状态为级联参数 ”部分。

AuthorizeRouteView 接收资源的路由数据时,授权策略可访问允许自定义逻辑做出授权决策的 RouteData.PageTypeRouteData.RouteValues

在下面的示例中,通过以下逻辑在 EditUser 中为应用的授权服务配置 (AuthorizationOptions) 创建 AddAuthorizationCore 策略:

  • 确定是否存在包含密钥 id 的路由值。 如果存在该密钥,则路由值存储在 value 中。
  • 在名为 id 的变量中,将 value 存储为字符串,或者设置空字符串值 (string.Empty)。
  • 如果 id 不是空字符串,则在字符串的值以 true 开头时断言符合策略(返回 EMP)。 否则,断言策略失败(返回 false)。

Program 文件中:

  • 添加 Microsoft.AspNetCore.ComponentsSystem.Linq 的命名空间:

    using Microsoft.AspNetCore.Components;
    using System.Linq;
    
  • 添加政策:

    options.AddPolicy("EditUser", policy =>
        policy.RequireAssertion(context =>
        {
            if (context.Resource is RouteData rd)
            {
                var routeValue = rd.RouteValues.TryGetValue("id", out var value);
                var id = Convert.ToString(value, 
                    System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty;
    
                if (!string.IsNullOrEmpty(id))
                {
                    return id.StartsWith("EMP", StringComparison.InvariantCulture);
                }
            }
    
            return false;
        })
    );
    

上述示例是一个过于简单的授权策略,仅用于通过工作示例演示概念。 有关创建和配置授权策略的详细信息,请参阅 ASP.NET Core 中的基于策略的授权

在以下 EditUser 组件中,位于 /users/{id}/edit 的资源具有用户标识符的路由参数 ({id})。 该组件使用前面的 EditUser 授权策略来确定 id 的路由值是否以 EMP 开头。 如果 idEMP 开头,则策略成功,并且已授权访问组件。 如果 id 不以 EMP 值开头,或者 id 是空字符串,则策略失败,并且不会加载组件。

EditUser.razor:

@page "/users/{id}/edit"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "EditUser")]

<h1>Edit User</h1>

<p>The "EditUser" policy is satisfied! <code>Id</code> starts with 'EMP'.</p>

@code {
    [Parameter]
    public string? Id { get; set; }
}
@page "/users/{id}/edit"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "EditUser")]

<h1>Edit User</h1>

<p>The "EditUser" policy is satisfied! <code>Id</code> starts with 'EMP'.</p>

@code {
    [Parameter]
    public string? Id { get; set; }
}

使用 Router 组件自定义未经授权的内容

Router 组件与 AuthorizeRouteView 组件搭配使用时,可允许应用程序在以下情况下指定自定义内容:

重要

Blazor 显示 <NotAuthorized><NotFound> 内容的路由器功能在静态服务器端呈现(静态 SSR)期间无法运作,因为请求处理完全由 ASP.NET Core 中间件管道进行,而对于未经授权或错误的请求,Razor 组件根本不会被呈现。 使用服务器端技术在静态 SSR 期间处理未经授权的和错误的请求。 有关详细信息,请参阅 ASP.NET 核心 Blazor 呈现模式

<Router ...>
    <Found ...>
        <AuthorizeRouteView ...>
            <NotAuthorized>
                ...
            </NotAuthorized>
            <Authorizing>
                ...
            </Authorizing>
        </AuthorizeRouteView>
    </Found>
</Router>

AuthorizedNotAuthorized 的内容可以包括任意项,如其他交互式组件。

注意

上面的内容要求在应用的 Program 文件中注册级联身份验证状态服务:

builder.Services.AddCascadingAuthenticationState();
<CascadingAuthenticationState>
    <Router ...>
        <Found ...>
            <AuthorizeRouteView ...>
                <NotAuthorized>
                    ...
                </NotAuthorized>
                <Authorizing>
                    ...
                </Authorizing>
            </AuthorizeRouteView>
        </Found>
    </Router>
</CascadingAuthenticationState>

NotFoundAuthorizedNotAuthorized 的内容可以包括任意项,如其他交互式组件。

如果未指定 NotAuthorized 内容,AuthorizeRouteView 就会使用以下回退消息:

Not authorized.

从启用了身份验证的 Blazor WebAssembly 项目模板创建的应用包含一个 RedirectToLogin 组件,它位于 <NotAuthorized> 组件的 Router 内容中。 当用户未进行身份验证 (context.User.Identity?.IsAuthenticated != true) 时,RedirectToLogin 组件会将浏览器重定向到 authentication/login 终结点以进行身份验证。 使用标识提供者进行身份验证后,用户将返回到请求的 URL。

过程逻辑

如果需要应用在过程逻辑中检查授权规则,请使用类型为 Task<AuthenticationState> 的级联参数来获取用户的 ClaimsPrincipalTask< AuthenticationState > 可以与其他服务(如 IAuthorizationService)结合使用来评估策略。

在下面的示例中:

  • user.Identity.IsAuthenticated 为经过身份验证(已登录)的用户执行代码。
  • user.IsInRole("admin") 为担任“管理员”角色的用户执行代码。
  • (await AuthorizationService.AuthorizeAsync(user, "content-editor")).Succeeded 为满足“content-editor”策略的用户执行代码。

从项目模板创建时,服务器端 Blazor 应用包含相应的命名空间。 在客户端 Blazor 应用中,确认组件或应用的 Microsoft.AspNetCore.Authorization 文件中是否存在 Microsoft.AspNetCore.Components.Authorization_Imports.razor 命名空间:

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization

ProceduralLogic.razor:

@page "/procedural-logic"
@inject IAuthorizationService AuthorizationService

<h1>Procedural Logic Example</h1>

<button @onclick="@DoSomething">Do something important</button>

@code {
    [CascadingParameter]
    private Task<AuthenticationState>? authenticationState { get; set; }

    private async Task DoSomething()
    {
        if (authenticationState is not null)
        {
            var authState = await authenticationState;
            var user = authState?.User;

            if (user is not null)
            {
                if (user.Identity is not null && user.Identity.IsAuthenticated)
                {
                    // ...
                }

                if (user.IsInRole("Admin"))
                {
                    // ...
                }

                if ((await AuthorizationService.AuthorizeAsync(user, "content-editor"))
                    .Succeeded)
                {
                    // ...
                }
            }
        }
    }
}
@page "/procedural-logic"
@inject IAuthorizationService AuthorizationService

<h1>Procedural Logic Example</h1>

<button @onclick="@DoSomething">Do something important</button>

@code {
    [CascadingParameter]
    private Task<AuthenticationState>? authenticationState { get; set; }

    private async Task DoSomething()
    {
        if (authenticationState is not null)
        {
            var authState = await authenticationState;
            var user = authState?.User;

            if (user is not null)
            {
                if (user.Identity is not null && user.Identity.IsAuthenticated)
                {
                    // ...
                }

                if (user.IsInRole("Admin"))
                {
                    // ...
                }

                if ((await AuthorizationService.AuthorizeAsync(user, "content-editor"))
                    .Succeeded)
                {
                    // ...
                }
            }
        }
    }
}

排查错误

常见错误:

  • 需要 Task<AuthenticationState> 类型的级联参数才能进行授权。 请考虑使用 CascadingAuthenticationState 来提供此参数。

  • null,收到了 authenticationStateTask

项目可能不是使用启用了身份验证的服务器端 Blazor 模板创建的。

在 .NET 7 或更低版本中,请使用 <CascadingAuthenticationState> 包装 UI 树的某些部分,例如包装 Blazor 路由器:

<CascadingAuthenticationState>
    <Router ...>
        ...
    </Router>
</CascadingAuthenticationState>

在 .NET 8 中或更高版本中,请勿使用 CascadingAuthenticationState 组件:

- <CascadingAuthenticationState>
      <Router ...>
          ...
      </Router>
- </CascadingAuthenticationState>

而是应当将级联身份验证状态服务添加到 Program 文件中的服务集合:

builder.Services.AddCascadingAuthenticationState();

CascadingAuthenticationState 组件(.NET 7 或更低版本)或 AddCascadingAuthenticationState 提供的服务(.NET 8 或更高版本)提供了 Task<AuthenticationState> 级联参数,它随后从基础 AuthenticationStateProvider 依赖项注入服务接收该参数的值。

个人身份信息 (PII)

当文档讨论个人身份信息(PII)时,Microsoft对 “个人数据”(GDPR 4.1)使用 GDPR 定义

PII 是指与已识别或可识别的自然人相关的任何信息。 可识别的自然人是可通过以下内容直接或间接识别的个人:

  • 名称
  • 身份证号
  • 位置坐标
  • 联机标识符
  • 其他特定因素
    • 物理
    • 生理特征
    • 遗传
    • 心理特征
    • 经济
    • 文化
    • 社会身份

其他资源