本文介绍如何授权用户访问应用资源。
在应用中, 资源 通常由 C# 类表示,该类包含集合中存储的数据,例如 byte[] 数组。 该类通常包含与资源相关的其他元数据,例如唯一的资源标识符、日期、作者、源信息和 UI 中显示的友好名称。 保存资源数据的集合通常从物理文件内容、云存储对象、内存中对象或数据库中的数据加载。
基于资源的授权在 ASP.NET Core应用中需要特别注意。 属性评估发生在数据绑定之前,在执行加载资源的任何方法之前。 使用 [Authorize] 特性的声明式授权不足以满足基于资源的授权要求。 相反,应用必须调用自定义授权方法,即称为 命令性授权的方法。
本文使用 Razor 组件示例,重点介绍 ASP.NET Core 3.1 或更高版本的 Blazor 授权方案。 有关适用于 ASP.NET Core的所有版本的 Razor Pages 和 MVC 指南,请参阅以下资源:
本文中的示例使用 C# 12 (.NET 8) 或更高版本中提供 的主要构造函数。 有关详细信息,请参阅声明类和结构的主要构造函数(C# 文档教程)和主要构造函数(C# 指南)。
示例应用
Blazor Web App本文的示例是BlazorWebAppAuthorization示例应用(dotnet/AspNetCore.Docs.SamplesGitHub 存储库)(如何下载)。 示例应用使用具有预配置文档对象的种子帐户来演示本文中的示例。 有关详细信息,请参阅示例的自述文件(README.md)。
Caution
此示例应用使用内存中数据库来存储用户信息,这不适合生产方案。 示例应用仅用于演示目的,不应用作生产应用的起点。
使用命令性授权
授权是以 IAuthorizationService 的形式实现的,该 IAuthorizationService 在应用启动时由 ASP.NET Core 框架 注册到服务集合中。 该服务通过Razor提供给组件和其他类:
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
IAuthorizationService 有两个 AuthorizeAsync 方法重载。 其中一个重载接受资源和策略名称:
Task<AuthorizationResult> AuthorizeAsync(
ClaimsPrincipal user,
object resource,
string policyName);
另一个重载接受资源和要评估的要求 (IAuthorizationRequirement) 集合:
Task<AuthorizationResult> AuthorizeAsync(
ClaimsPrincipal user,
object resource,
IEnumerable<IAuthorizationRequirement> requirements);
在下面的示例中,在 “创建基于资源的处理程序 ”部分中进行了完全解释,受保护的资源将加载到自定义 Document 对象中。 将调用 AuthorizeAsync 重载来确定是否允许当前用户根据“SameAuthorPolicy”授权策略访问文档。 如果 authorizationResult.Succeeded 是 true,则用户有权访问该文档,因为他们编写了该文档(Document.Author 与用户的 Name 匹配):
protected override async Task OnParametersSetAsync()
{
var user = (await AuthStateProvider.GetAuthenticationStateAsync()).User;
if (user.Identity is not null && user.Identity.IsAuthenticated)
{
var document = DocumentRepository.Find(DocumentId);
...
var authorizationResult = await AuthorizationService
.AuthorizeAsync(user, document, "SameAuthorPolicy");
...
}
}
创建基于资源的处理程序
创建基于资源的授权处理程序类似于 创建普通要求处理程序。 创建自定义要求类并实现要求处理程序类。 有关创建要求类的详细信息,请参阅 ASP.NET Core中基于策略的授权。
使用以下用于演示的 Document 类:
namespace BlazorWebAppAuthorization.Models;
public class Document
{
public string? Author { get; set; }
public byte[]? Content { get; set; }
public Guid ID { get; set; }
public string? Title { get; set; }
}
处理程序类指定要求和资源类型。 以下示例演示了一个利用 SameAuthorRequirement 要求和 Document 资源的处理程序。
Policies/Handlers/DocumentAuthorizationHandler.cs:
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Models;
namespace BlazorWebAppAuthorization.Policies.Handlers;
public class DocumentAuthorizationHandler :
AuthorizationHandler<SameAuthorRequirement, Document>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
SameAuthorRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class SameAuthorRequirement : IAuthorizationRequirement { }
在 Program.cs 中注册要求和处理程序:
builder.Services.AddAuthorizationBuilder()
.AddPolicy("SameAuthorPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
在 Startup.ConfigureServices 中注册要求和处理程序:
services.AddAuthorization(options =>
{
options.AddPolicy("SameAuthorPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
});
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
有关创建授权策略的详细信息,请参阅 ASP.NET Core 中基于策略的授权。
以下 AccessDocument 组件调用 AuthorizeAsync 重载,以确定是否允许当前用户基于“SameAuthorPolicy”授权策略查看文档。 如果 authorizationResult.Succeeded 为 true,则用户已获得该文档的访问授权,因为用户编写了该文档(Document.Author 与用户的 Name 匹配)。
Pages/AccessDocument.razor:
@page "/access-document/{documentId}"
@using Microsoft.AspNetCore.Authorization
@using BlazorWebAppAuthorization.Data
@inject AuthenticationStateProvider AuthStateProvider
@inject IAuthorizationService AuthorizationService
@inject IDocumentRepository DocumentRepository
<h1>Access Document</h1>
<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
<p>@message</p>
</Authorized>
<NotAuthorized>
<p>You're not authorized to access this page.</p>
</NotAuthorized>
</AuthorizeView>
@code {
private string? message;
[Parameter]
public string? DocumentId { get; set; }
protected override async Task OnParametersSetAsync()
{
var user = (await AuthStateProvider.GetAuthenticationStateAsync()).User;
if (user.Identity is not null && user.Identity.IsAuthenticated)
{
var document = DocumentRepository.Find(DocumentId);
if (document == null)
{
message = "Document not found.";
return;
}
var authorizationResult = await AuthorizationService
.AuthorizeAsync(user, document, "SameAuthorPolicy");
message = authorizationResult.Succeeded
? $"You are authorized for document {DocumentId}."
: $"You are NOT authorized for document {DocumentId}.";
}
}
}
在 示例应用中,应用的每个用户都有权访问他们创作的种子文档。
操作要求
若要根据 CRUD(创建、读取、更新、删除)操作的结果做出决策,请使用 OperationAuthorizationRequirement 帮助程序类。 借助帮助程序类,可以为每个操作类型编写单个处理程序而不是单个类。 以下 Operations 类建立所有四种 CRUD 操作类型:
using Microsoft.AspNetCore.Authorization.Infrastructure;
public static class Operations
{
public static readonly OperationAuthorizationRequirement Create =
new() { Name = nameof(Create) };
public static readonly OperationAuthorizationRequirement Delete =
new() { Name = nameof(Delete) };
public static readonly OperationAuthorizationRequirement Read =
new() { Name = nameof(Read) };
public static readonly OperationAuthorizationRequirement Update =
new() { Name = nameof(Update) };
}
以下 DocumentAuthorizationCrudHandler 授权处理程序根据资源、在某些情况下还根据用户的身份(角色)以及要求的 Name 属性来验证该操作:
- 所有用户都可以阅读文档。
- 只有角色中的
Admin用户可以创建和更新文档。 - 只有具有
SuperUser角色的用户才能删除文档。
Policies/Handlers/DocumentAuthorizationCrudHandler.cs:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using BlazorWebAppAuthorization.Models;
namespace BlazorWebAppAuthorization.Policies.Handlers;
public class DocumentAuthorizationCrudHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (requirement.Name == Operations.Create.Name &&
context.User.IsInRole("Admin"))
{
context.Succeed(requirement);
}
if (requirement.Name == Operations.Delete.Name &&
context.User.IsInRole("SuperUser"))
{
context.Succeed(requirement);
}
if (requirement.Name == Operations.Read.Name)
{
context.Succeed(requirement);
}
if (requirement.Name == Operations.Update.Name &&
context.User.IsInRole("Admin"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
在应用中注册服务的位置:
builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
调用含操作的 AuthorizeAsync 重载,以返回授权结果。
若要获得 创建 文档的授权,请执行:
var authorizationResult = await AuthorizationService
.AuthorizeAsync(user, document, Operations.Create);
若要获得 读取 文档的授权,请执行以下操作:
var authorizationResult = await AuthorizationService
.AuthorizeAsync(user, document, Operations.Read);
若要授权 删除 文档:
var authorizationResult = await AuthorizationService
.AuthorizeAsync(user, document, Operations.Delete);
若要获得 更新 文档的授权,
var authorizationResult = await AuthorizationService
.AuthorizeAsync(user, document, Operations.Update);
在示例应用的AccessDocumentCrud页面中:
- Leela (
leela@contoso.com) 作为Admin和SuperUser,可以对资源执行完整 CRUD 操作。 - Harry (
harry@contoso.com) 仅作为Admin,可以创建、读取和更新资源。 - Sarah (
sarah@contoso.com) 仅作为SuperUser,可以删除和读取资源。