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

授权策略取决于要访问的资源。Authorization strategy depends upon the resource being accessed. 假设有一个具有 author 属性的文档。Consider a document that has an author property. 仅允许作者更新文档。Only the author is allowed to update the document. 因此,在进行授权评估之前,必须从数据存储中检索文档。Consequently, the document must be retrieved from the data store before authorization evaluation can occur.

在数据绑定之前和在执行加载文档的页面处理程序或操作之前,会发生属性评估。Attribute evaluation occurs before data binding and before execution of the page handler or action that loads the document. 由于这些原因,具有属性的声明性授权 [Authorize] 无法满足要求。For these reasons, declarative authorization with an [Authorize] attribute doesn't suffice. 相反,你可以调用自定义授权方法, — 这种方法称为 命令式 authorizationInstead, you can invoke a custom authorization method—a style known as imperative authorization.

使用由授权保护的用户数据创建 ASP.NET Core 应用包含使用基于资源的授权的示例应用。Create an ASP.NET Core app with user data protected by authorization contains a sample app that uses resource-based authorization.

使用命令性授权Use imperative authorization

授权作为 IAuthorizationService 服务实现,并在类中的服务集合中进行注册 StartupAuthorization is implemented as an IAuthorizationService service and is registered in the service collection within the Startup class. 通过 依赖关系注入 到页面处理程序或操作使该服务可用。The service is made available via dependency injection to page handlers or actions.

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
                              IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

IAuthorizationService 具有两个 AuthorizeAsync 方法重载:一个接受资源和策略名称,另一个接受资源并提供要评估的要求的列表。IAuthorizationService has two AuthorizeAsync method overloads: one accepting the resource and the policy name and the other accepting the resource and a list of requirements to evaluate.

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          string policyName);
Task<bool> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          IEnumerable<IAuthorizationRequirement> requirements);
Task<bool> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          string policyName);

在下面的示例中,要保护的资源将加载到自定义 Document 对象中。In the following example, the resource to be secured is loaded into a custom Document object. AuthorizeAsync调用重载来确定是否允许当前用户编辑提供的文档。An AuthorizeAsync overload is invoked to determine whether the current user is allowed to edit the provided document. 将自定义 "EditPolicy" 授权策略分解为决定。A custom "EditPolicy" authorization policy is factored into the decision. 有关创建授权策略的详细信息,请参阅 基于策略的自定义授权See Custom policy-based authorization for more on creating authorization policies.

备注

下面的代码示例假定已运行身份验证并设置了 User 属性。The following code samples assume authentication has run and set the User property.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, "EditPolicy");

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}
[HttpGet]
public async Task<IActionResult> Edit(Guid documentId)
{
    Document document = _documentRepository.Find(documentId);

    if (document == null)
    {
        return new NotFoundResult();
    }

    if (await _authorizationService
        .AuthorizeAsync(User, document, "EditPolicy"))
    {
        return View(document);
    }
    else
    {
        return new ChallengeResult();
    }
}

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

为基于资源的授权编写处理程序与 编写简单的要求处理程序并无差别。Writing a handler for resource-based authorization isn't much different than writing a plain requirements handler. 创建自定义要求类,并实现需求处理程序类。Create a custom requirement class, and implement a requirement handler class. 有关创建要求类的详细信息,请参阅 要求For more information on creating a requirement class, see Requirements.

处理程序类同时指定要求和资源类型。The handler class specifies both the requirement and resource type. 例如,使用和资源的处理程序 SameAuthorRequirement Document 如下所示:For example, a handler utilizing a SameAuthorRequirement and a Document resource follows:

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

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

在前面的示例中,假设 SameAuthorRequirement 是更为通用的类的特殊情况 SpecificAuthorRequirementIn the preceding example, imagine that SameAuthorRequirement is a special case of a more generic SpecificAuthorRequirement class. SpecificAuthorRequirement类 (未显示) 包含 Name 表示作者姓名的属性。The SpecificAuthorRequirement class (not shown) contains a Name property representing the name of the author. Name属性可以设置为当前用户。The Name property could be set to the current user.

在以下项中注册要求和处理程序 Startup.ConfigureServicesRegister the requirement and handler in Startup.ConfigureServices:

services.AddControllersWithViews();
services.AddRazorPages();

services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();
services.AddMvc();

services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();
services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();

操作要求Operational requirements

如果要根据 CRUD (创建、读取、更新、删除) 操作的结果做出决策,请使用 OperationAuthorizationRequirement 帮助器类。If you're making decisions based on the outcomes of CRUD (Create, Read, Update, Delete) operations, use the OperationAuthorizationRequirement helper class. 此类使你能够为每个操作类型编写单一处理程序而不是单个类。This class enables you to write a single handler instead of an individual class for each operation type. 若要使用它,请提供一些操作名称:To use it, provide some operation names:

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 DocumentThe handler is implemented as follows, using an OperationAuthorizationRequirement requirement and a Document resource:

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

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

前面的处理程序使用资源、用户的标识和要求的属性验证操作 NameThe preceding handler validates the operation using the resource, the user's identity, and the requirement's Name property.

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

本部分说明如何处理质询和禁止操作结果,以及质询和禁止的不同之处。This section shows how the challenge and forbid action results are processed and how challenge and forbid differ.

若要调用操作资源处理程序,请 AuthorizeAsync 在页面处理程序或操作中调用时指定操作。To call an operational resource handler, specify the operation when invoking AuthorizeAsync in your page handler or action. 下面的示例确定是否允许经过身份验证的用户查看所提供的文档。The following example determines whether the authenticated user is permitted to view the provided document.

备注

下面的代码示例假定已运行身份验证并设置了 User 属性。The following code samples assume authentication has run and set the User property.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

如果授权成功,则返回用于查看文档的页面。If authorization succeeds, the page for viewing the document is returned. 如果授权失败,但用户已进行身份验证,则返回 ForbidResult 通知任何身份验证中间件验证失败。If authorization fails but the user is authenticated, returning ForbidResult informs any authentication middleware that authorization failed. ChallengeResult当必须执行身份验证时,将返回。A ChallengeResult is returned when authentication must be performed. 对于交互式浏览器客户端,可能需要将用户重定向到登录页。For interactive browser clients, it may be appropriate to redirect the user to a login page.

[HttpGet]
public async Task<IActionResult> View(Guid documentId)
{
    Document document = _documentRepository.Find(documentId);

    if (document == null)
    {
        return new NotFoundResult();
    }

    if (await _authorizationService
        .AuthorizeAsync(User, document, Operations.Read))
    {
        return View(document);
    }
    else
    {
        return new ChallengeResult();
    }
}

如果授权成功,则返回文档的视图。If authorization succeeds, the view for the document is returned. 如果授权失败,则返回 ChallengeResult 通知任何身份验证中间件身份验证失败,中间件可以采取相应的响应。If authorization fails, returning ChallengeResult informs any authentication middleware that authorization failed, and the middleware can take the appropriate response. 适当的响应可能返回401或403状态代码。An appropriate response could be returning a 401 or 403 status code. 对于交互式浏览器客户端,这可能意味着将用户重定向到登录页。For interactive browser clients, it could mean redirecting the user to a login page.