本文介绍如何授权用户访问应用资源。
在应用中, 资源 通常由 C# 类表示,该类包含集合中存储的数据,例如 byte[] 数组。 该类通常包含与资源相关的其他元数据,例如唯一的资源标识符、日期、作者、源信息和 UI 中显示的友好名称。 保存资源数据的集合通常从物理文件内容、云存储对象、内存中对象或数据库中的数据加载。
基于资源的授权在 ASP.NET Core应用中需要特别注意。 属性评估发生在数据绑定之前,在执行加载资源的页面处理程序之前。 使用 [Authorize] 特性的声明式授权不足以满足基于资源的授权要求。 相反,应用必须调用自定义授权方法,即称为 命令性授权的方法。
创建一个使用授权保护用户数据的 ASP.NET Core 应用包含一个使用基于资源的授权的示例应用程序。
本文中的示例使用 C# 12 (.NET 8) 或更高版本中提供 的主要构造函数。 有关详细信息,请参阅声明类和结构的主要构造函数(C# 文档教程)和主要构造函数(C# 指南)。 随附于该文章、以早于 .NET 8 的 .NET 版本为目标的示例应用使用构造函数注入。
使用命令性授权
授权是以 IAuthorizationService 的形式实现的,该 IAuthorizationService 在应用启动时由 ASP.NET Core 框架 注册到服务集合中。 该服务通过 依赖项注入提供给类和页面处理程序。 以下页面处理程序还会注入一个文档存储库,开发人员在服务容器中创建并注册该存储库来管理文档操作:
public class DocumentModel(IAuthorizationService authorizationService,
IDocumentRepository documentRepository) : PageModel
{
private readonly IAuthorizationService _authorizationService;
private readonly IDocumentRepository _documentRepository;
public DocumentModel(IAuthorizationService authorizationService,
IDocumentRepository documentRepository)
{
_authorizationService = authorizationService;
_documentRepository = documentRepository;
}
...
}
IAuthorizationService 有两种 AuthorizeAsync 方法重载。 其中一个重载接受资源和策略名称:
Task<AuthorizationResult> AuthorizeAsync(
ClaimsPrincipal user,
object resource,
string policyName);
另一个重载接受资源和要评估的要求 (IAuthorizationRequirement) 集合:
Task<AuthorizationResult> AuthorizeAsync(
ClaimsPrincipal user,
object resource,
IEnumerable<IAuthorizationRequirement> requirements);
在以下示例中,安全资源将加载到自定义 Document 对象中。 调用 AuthorizeAsync 重载来确定当前用户是否可通过自定义“EditPolicy”授权策略编辑该文档。 如果 authorizationResult.Succeeded 为 true,则用户已获得该文档的访问授权,因为用户编写了该文档(Document.Author 与用户的 Name 匹配)。
注释
以下示例假定使用属性集成功进行身份验证 User 。
public async Task<IActionResult> OnGetAsync(Guid documentId)
{
Document = _documentRepository.Find(documentId);
...
var authorizationResult = await _authorizationService
.AuthorizeAsync(User, Document, "EditPolicy");
...
}
创建基于资源的处理程序
创建基于资源的授权处理程序类似于 创建普通要求处理程序。 创建自定义要求类并实现要求处理程序类。 有关创建要求类的详细信息,请参阅 ASP.NET Core中基于策略的授权。
处理程序类指定要求和资源类型。 以下示例演示了一个利用 SameAuthorRequirement 要求和 Document 资源的处理程序:
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 { }
在前面的示例中,假设 SameAuthorRequirement 是更泛型的 SpecificAuthorRequirement 类的特例。
SpecificAuthorRequirement 类(未显示)包含一个表示作者姓名的 Name 属性。
Name 属性可以设置为当前用户。
在 Program.cs 中注册要求和处理程序:
builder.Services.AddAuthorizationBuilder()
.AddPolicy("EditPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
在 Startup.ConfigureServices 中注册要求和处理程序:
services.AddAuthorization(options =>
{
options.AddPolicy("EditPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
});
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
有关创建授权策略的详细信息,请参阅 ASP.NET Core 中基于策略的授权。
操作要求
若要根据 CRUD(创建、读取、更新、删除)操作的结果做出决策,请使用 OperationAuthorizationRequirement 帮助程序类。 此类使你可以编写一个处理程序,而不必为每种操作类型分别编写一个类。 若要使用它,请提供一些操作名称:
public static class Operations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement { Name = nameof(Create) };
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement { Name = nameof(Read) };
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement { Name = nameof(Update) };
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement { Name = nameof(Delete) };
}
使用 OperationAuthorizationRequirement 要求和 Document 资源实现处理程序,如下所示:
public class DocumentAuthorizationCrudHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author &&
requirement.Name == Operations.Read.Name)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
前面的处理程序使用资源、用户的标识和要求 Name 属性验证操作。
操作资源处理程序的挑战和禁止
本部分介绍如何处理挑战和禁止结果,以及挑战和禁止的差异。
授权失败但用户经过身份验证时,应用可以返回一个 ForbidResult通知身份验证中间件授权失败。 对于未经身份验证的用户,返回一个 ChallengeResult。 对于交互式浏览器客户端,可能适合将用户重定向到登录页面。
注释
以下示例假定使用属性集成功进行身份验证 User 。
var authorizationResult = await _authorizationService
.AuthorizeAsync(User, Document, Operations.Read);
if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity?.IsAuthenticated ?? false)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}