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

注意

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

重要

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

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

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

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

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

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

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

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

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

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

注意

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

防伪支持

Blazor 模板:

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

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

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

注意

只有当将表单数据以编码为 application/x-www-form-urlencodedmultipart/form-datatext/plain 的形式提交到服务器时,才要求防伪迁移,因为这些形式是唯一有效的表单 enctype

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

身份验证

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

服务器端 Blazor 身份验证

以交互方式呈现的服务器端 Blazor 通过与客户端的 SignalR 连接运行。 建立连接后,将处理基于 SignalR 的应用的身份验证。 身份验证可以基于 cookie 或某个其他持有者令牌,但身份验证通过 SignalR 中心和完全在线路中进行管理。

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

Razor 组件中的 IHttpContextAccessor/HttpContext

必须避免将 IHttpContextAccessor 与交互式呈现一起使用,因为无有效的 HttpContext 可用。

IHttpContextAccessor 可用于在服务器上静态呈现的组件。 但是,建议尽可能避免这种做法。

HttpContext 只能在常规任务的静态呈现根组件中用作级联参数,这些任务包括检查和修改 App 组件 (Components/App.razor) 中的标头或其他属性。 对于交互式呈现,值始终是 null

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

对于交互式组件中需要 HttpContext 的场景,建议通过服务器的永久性组件状态传输数据。 有关详细信息,请参阅服务器端 ASP.NET Core Blazor 其他安全方案

请勿在服务器端 Blazor 应用的 Razor 组件中直接或间接使用 IHttpContextAccessor/HttpContext Blazor 应用在 ASP.NET Core 管道上下文之外运行。 既不保证 HttpContextIHttpContextAccessor 中可用,也不保证 HttpContext 会保留启动了 Blazor 应用的上下文。

建议在 Blazor 应用的初始呈现期间通过根组件参数将请求状态传递给此应用。 或者,应用可以将数据复制到根组件的初始化生命周期事件中的作用域内服务中,以便在整个应用中使用。 有关详细信息,请参阅服务器端 ASP.NET Core Blazor 其他安全方案

服务器端 Blazor 安全性的一个关键方面是,连接到给定线路的用户可能会在建立 Blazor 线路后的某个时间点得到更新,但 IHttpContextAccessor 不会更新。 有关使用自定义服务应对此情况的详细信息,请参阅服务器端 ASP.NET Core Blazor 其他安全方案

共享状态

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

警告

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

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

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

客户端 Blazor 身份验证

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

添加以下内容:

为处理身份验证,需使用内置或自定义 AuthenticationStateProvider 服务,以下几节对此进行了介绍。

有关详细信息,请参阅保护 ASP.NET Core Blazor WebAssembly

AuthenticationStateProvider 服务

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

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

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

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

ClaimsPrincipalData.razor

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

<h1>ClaimsPrincipal Data</h1>

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

<p>@authMessage</p>

@if (claims.Count() > 0)
{
    <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-principle-data"
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>ClaimsPrincipal Data</h1>

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

<p>@authMessage</p>

@if (claims.Count() > 0)
{
    <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.";
        }
    }
}

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

有关依赖关系注入 (DI) 和服务的详细信息,请参阅 ASP.NET Core Blazor 依赖关系注入ASP.NET Core 中的依赖关系注入。 有关如何在服务器端 Blazor 应用中实现自定义 AuthenticationStateProvider 的信息,请参阅安全 ASP.NET Core 服务器端 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.IsAuthenticatedtrue,可以枚举声明并评估角色成员身份。

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

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

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

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

builder.Services.AddCascadingAuthenticationState();

使用 AuthorizeRouteViewCascadingAuthenticationState 组件设置Task<AuthenticationState>级联参数

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

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

注意

随着 ASP.NET Core 5.0.1 的发布及任何附加 5.x 版本的推出,Router 组件包含 PreferExactMatches 参数(设置为 @true)。 有关详细信息,请参阅从 ASP.NET Core 3.1 迁移到 5.0

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

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

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

授权

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

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

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

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

AuthorizeView 组件

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

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

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

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

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

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

授权元素的默认事件处理程序(例如上例中 <button> 元素的 SecureMethod 方法)仅可由已获得授权的用户进行调用。

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

警告

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

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

授权一节中介绍了授权条件,如用于控制 UI 选项或访问权限的角色或策略。

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

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

可以在 NavMenu 组件 (Shared/NavMenu.razor) 中使用 AuthorizeView 组件来显示 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>

前面的代码为内部 AuthorizeView 组件建立 Context,防止出现 AuthenticationState 上下文冲突。 在外部 AuthorizeView 中,AuthenticationState 上下文是通过用于访问上下文 (@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 均未指定,则 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] 特性

[Authorize] 特性在 Razor 组件中可用:

@page "/"
@attribute [Authorize]

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

重要

请仅在通过 Blazor 路由器到达的 @page 组件上使用 [Authorize]。 授权仅作为路由的一个方面执行,而不是作为页面中呈现的子组件来执行。 若要授权在页面中显示特定部分,请改用 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] 使用默认策略:

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

当用户未获得授权并且应用未使用路由器组件自定义未授权的内容时,框架会自动显示以下回退消息:

Not authorized.

资源授权

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

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

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

若要详细了解如何在过程逻辑中传递和使用授权状态数据,请参阅公开身份验证状态作为级联参数部分。

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

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

  • 确定是否存在包含密钥 id 的路由值。 如果存在该密钥,则路由值存储在 value 中。
  • 在名为 id 的变量中,将 value 存储为字符串,或者设置空字符串值 (string.Empty)。
  • 如果 id 不是空字符串,则在字符串的值以 EMP 开头时断言符合策略(返回 true)。 否则,断言策略失败(返回 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 组件与 AuthorizeRouteView 组件搭配使用时,可允许应用程序在以下情况下指定自定义内容:

重要

Blazor 显示 <NotAuthorized><NotFound> 内容的路由器功能在静态服务器端呈现(静态 SSR)期间无法运行,因为请求处理完全由 ASP.NET 核心中间件管道请求处理进行处理,而对于未经授权或错误的请求,根本不会呈现 Razor 组件。 使用服务器端技术在静态 SSR 期间处理未经授权的和错误的请求。 有关详细信息,请参阅 ASP.NET Core 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 组件,它位于 Router 组件的 <NotAuthorized> 内容中。 当用户未进行身份验证 (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 应用中,确认组件或应用的 _Imports.razor 文件中是否存在 Microsoft.AspNetCore.AuthorizationMicrosoft.AspNetCore.Components.Authorization 命名空间:

@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 来提供此参数。

  • 对于 authenticationStateTask,收到了 null

项目可能不是使用启用了身份验证的服务器端 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 的“个人数据”定义 (GDPR 4.1)

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

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

其他资源