ASP.NET Core 中基于资源的授权Resource-based authorization in ASP.NET Core

本文内容

授权策略取决于要访问的资源。假设有一个具有 author 属性的文档。仅允许作者更新文档。因此,在进行授权评估之前,必须从数据存储中检索文档。

在数据绑定之前和在执行加载文档的页面处理程序或操作之前,会发生属性评估。由于这些原因,具有 [Authorize] 特性的声明性授权无法满足要求。相反,你可以调用自定义授权方法—称为命令式授权的样式。

查看或下载示例代码如何下载)。

查看或下载示例代码如何下载)。

查看或下载示例代码如何下载)。

使用由授权保护的用户数据创建 ASP.NET Core 应用包含使用基于资源的授权的示例应用。

使用命令性授权Use imperative authorization

授权作为IAuthorizationService服务实现,并在 Startup 类中的服务集合中进行注册。通过依赖关系注入到页面处理程序或操作使该服务可用。

  1. public class DocumentController : Controller
  2. {
  3. private readonly IAuthorizationService _authorizationService;
  4. private readonly IDocumentRepository _documentRepository;
  5. public DocumentController(IAuthorizationService authorizationService,
  6. IDocumentRepository documentRepository)
  7. {
  8. _authorizationService = authorizationService;
  9. _documentRepository = documentRepository;
  10. }

IAuthorizationService 有两个 AuthorizeAsync 方法重载:一个接受资源和策略名称,另一个接受资源并提供要评估的要求的列表。

  1. Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
  2. object resource,
  3. IEnumerable<IAuthorizationRequirement> requirements);
  4. Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
  5. object resource,
  6. string policyName);
  1. Task<bool> AuthorizeAsync(ClaimsPrincipal user,
  2. object resource,
  3. IEnumerable<IAuthorizationRequirement> requirements);
  4. Task<bool> AuthorizeAsync(ClaimsPrincipal user,
  5. object resource,
  6. string policyName);

在下面的示例中,要保护的资源将加载到自定义的 Document 对象。调用 AuthorizeAsync 重载来确定是否允许当前用户编辑提供的文档。将自定义 "EditPolicy" 授权策略分解为决定。有关创建授权策略的详细信息,请参阅基于策略的自定义授权

备注

下面的代码示例假定已运行身份验证,并设置 User 属性。

  1. public async Task<IActionResult> OnGetAsync(Guid documentId)
  2. {
  3. Document = _documentRepository.Find(documentId);
  4. if (Document == null)
  5. {
  6. return new NotFoundResult();
  7. }
  8. var authorizationResult = await _authorizationService
  9. .AuthorizeAsync(User, Document, "EditPolicy");
  10. if (authorizationResult.Succeeded)
  11. {
  12. return Page();
  13. }
  14. else if (User.Identity.IsAuthenticated)
  15. {
  16. return new ForbidResult();
  17. }
  18. else
  19. {
  20. return new ChallengeResult();
  21. }
  22. }
  1. [HttpGet]
  2. public async Task<IActionResult> Edit(Guid documentId)
  3. {
  4. Document document = _documentRepository.Find(documentId);
  5. if (document == null)
  6. {
  7. return new NotFoundResult();
  8. }
  9. if (await _authorizationService
  10. .AuthorizeAsync(User, document, "EditPolicy"))
  11. {
  12. return View(document);
  13. }
  14. else
  15. {
  16. return new ChallengeResult();
  17. }
  18. }

编写基于资源的处理程序Write a resource-based handler

为基于资源的授权编写处理程序与编写简单的要求处理程序并无差别。创建自定义要求类,并实现需求处理程序类。有关创建要求类的详细信息,请参阅要求

处理程序类同时指定要求和资源类型。例如,使用 SameAuthorRequirementDocument 资源的处理程序如下所示:

  1. public class DocumentAuthorizationHandler :
  2. AuthorizationHandler<SameAuthorRequirement, Document>
  3. {
  4. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  5. SameAuthorRequirement requirement,
  6. Document resource)
  7. {
  8. if (context.User.Identity?.Name == resource.Author)
  9. {
  10. context.Succeed(requirement);
  11. }
  12. return Task.CompletedTask;
  13. }
  14. }
  15. public class SameAuthorRequirement : IAuthorizationRequirement { }
  1. public class DocumentAuthorizationHandler :
  2. AuthorizationHandler<SameAuthorRequirement, Document>
  3. {
  4. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  5. SameAuthorRequirement requirement,
  6. Document resource)
  7. {
  8. if (context.User.Identity?.Name == resource.Author)
  9. {
  10. context.Succeed(requirement);
  11. }
  12. //TODO: Use the following if targeting a version of
  13. //.NET Framework older than 4.6:
  14. // return Task.FromResult(0);
  15. return Task.CompletedTask;
  16. }
  17. }
  18. public class SameAuthorRequirement : IAuthorizationRequirement { }

在前面的示例中,假设 SameAuthorRequirement 是更通用 SpecificAuthorRequirement 类的特例。SpecificAuthorRequirement 类(未显示)包含表示作者姓名的 Name 属性。Name 属性可以设置为当前用户。

Startup.ConfigureServices中注册要求和处理程序:

  1. services.AddControllersWithViews();
  2. services.AddRazorPages();
  3. services.AddAuthorization(options =>
  4. {
  5. options.AddPolicy("EditPolicy", policy =>
  6. policy.Requirements.Add(new SameAuthorRequirement()));
  7. });
  8. services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
  9. services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
  10. services.AddScoped<IDocumentRepository, DocumentRepository>();
  1. services.AddMvc();
  2. services.AddAuthorization(options =>
  3. {
  4. options.AddPolicy("EditPolicy", policy =>
  5. policy.Requirements.Add(new SameAuthorRequirement()));
  6. });
  7. services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
  8. services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
  9. services.AddScoped<IDocumentRepository, DocumentRepository>();
  1. services.AddAuthorization(options =>
  2. {
  3. options.AddPolicy("EditPolicy", policy =>
  4. policy.Requirements.Add(new SameAuthorRequirement()));
  5. });
  6. services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
  7. services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
  8. services.AddScoped<IDocumentRepository, DocumentRepository>();

操作要求Operational requirements

如果要根据 CRUD (创建、读取、更新、删除)操作的结果做出决策,请使用OperationAuthorizationRequirement帮助器类。此类使你能够为每个操作类型编写单一处理程序而不是单个类。若要使用它,请提供一些操作名称:

  1. public static class Operations
  2. {
  3. public static OperationAuthorizationRequirement Create =
  4. new OperationAuthorizationRequirement { Name = nameof(Create) };
  5. public static OperationAuthorizationRequirement Read =
  6. new OperationAuthorizationRequirement { Name = nameof(Read) };
  7. public static OperationAuthorizationRequirement Update =
  8. new OperationAuthorizationRequirement { Name = nameof(Update) };
  9. public static OperationAuthorizationRequirement Delete =
  10. new OperationAuthorizationRequirement { Name = nameof(Delete) };
  11. }

处理程序的实现方式如下:使用 OperationAuthorizationRequirement 要求和 Document 资源:

  1. public class DocumentAuthorizationCrudHandler :
  2. AuthorizationHandler<OperationAuthorizationRequirement, Document>
  3. {
  4. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  5. OperationAuthorizationRequirement requirement,
  6. Document resource)
  7. {
  8. if (context.User.Identity?.Name == resource.Author &&
  9. requirement.Name == Operations.Read.Name)
  10. {
  11. context.Succeed(requirement);
  12. }
  13. return Task.CompletedTask;
  14. }
  15. }
  1. public class DocumentAuthorizationCrudHandler :
  2. AuthorizationHandler<OperationAuthorizationRequirement, Document>
  3. {
  4. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  5. OperationAuthorizationRequirement requirement,
  6. Document resource)
  7. {
  8. if (context.User.Identity?.Name == resource.Author &&
  9. requirement.Name == Operations.Read.Name)
  10. {
  11. context.Succeed(requirement);
  12. }
  13. //TODO: Use the following if targeting a version of
  14. //.NET Framework older than 4.6:
  15. // return Task.FromResult(0);
  16. return Task.CompletedTask;
  17. }
  18. }

前面的处理程序使用资源、用户的标识和要求的 Name 属性验证操作。

操作资源处理程序的挑战和禁止Challenge and forbid with an operational resource handler

本部分说明如何处理质询和禁止操作结果,以及质询和禁止的不同之处。

若要调用操作资源处理程序,请在调用页面处理程序或操作中的 AuthorizeAsync 时指定操作。下面的示例确定是否允许经过身份验证的用户查看所提供的文档。

备注

下面的代码示例假定已运行身份验证,并设置 User 属性。

  1. public async Task<IActionResult> OnGetAsync(Guid documentId)
  2. {
  3. Document = _documentRepository.Find(documentId);
  4. if (Document == null)
  5. {
  6. return new NotFoundResult();
  7. }
  8. var authorizationResult = await _authorizationService
  9. .AuthorizeAsync(User, Document, Operations.Read);
  10. if (authorizationResult.Succeeded)
  11. {
  12. return Page();
  13. }
  14. else if (User.Identity.IsAuthenticated)
  15. {
  16. return new ForbidResult();
  17. }
  18. else
  19. {
  20. return new ChallengeResult();
  21. }
  22. }

如果授权成功,则返回用于查看文档的页面。如果授权失败但用户已通过身份验证,则返回 ForbidResult 通知任何身份验证中间件身份验证失败。当必须执行身份验证时,将返回 ChallengeResult对于交互式浏览器客户端,可能需要将用户重定向到登录页。

  1. [HttpGet]
  2. public async Task<IActionResult> View(Guid documentId)
  3. {
  4. Document document = _documentRepository.Find(documentId);
  5. if (document == null)
  6. {
  7. return new NotFoundResult();
  8. }
  9. if (await _authorizationService
  10. .AuthorizeAsync(User, document, Operations.Read))
  11. {
  12. return View(document);
  13. }
  14. else
  15. {
  16. return new ChallengeResult();
  17. }
  18. }

如果授权成功,则返回文档的视图。如果授权失败,则返回 ChallengeResult 会通知任何授权失败的身份验证中间件,中间件可以进行适当的响应。适当的响应可能返回401或403状态代码。对于交互式浏览器客户端,这可能意味着将用户重定向到登录页。