ASP.NET Core 中基于资源的授权

授权方法取决于资源。 例如,只有文档的作者有权更新文档。 因此,必须先从数据存储中检索文档,然后才能进行授权评估。

属性评估发生在数据绑定之前以及执行加载文档的页面处理程序或操作之前。 出于这些原因,使用 [Authorize] 属性进行声明性授权无法满足需求。 你可以调用自定义授权方法 - 一种称为命令性授权的样式。

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

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

使用命令性授权

授权作为服务 IAuthorizationService 实现,并在类中的服务集合 Startup 中注册。 该服务通过依赖项注入提供给页面处理程序或操作。

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

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

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

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

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

注意

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

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

编写基于资源的处理程序

为基于资源的授权编写处理程序与编写普通的要求处理程序没有太大区别。 创建自定义要求类并实现要求处理程序类。 有关创建要求类的详细信息,请参阅要求

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

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.AddRazorPages();
builder.Services.AddControllersWithViews();

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

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

操作要求

如果要根据 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 属性来验证操作。

操作资源处理程序的挑战和禁止

本部分说明如何处理挑战和禁止操作结果,以及挑战和禁止的区别。

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

注意

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

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

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

授权方法取决于资源。 例如,只有文档的作者有权更新文档。 因此,必须先从数据存储中检索文档,然后才能进行授权评估。

属性评估发生在数据绑定之前以及执行加载文档的页面处理程序或操作之前。 出于这些原因,使用 [Authorize] 属性进行声明性授权无法满足需求。 你可以调用自定义授权方法 - 一种称为命令性授权的样式。

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

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

使用命令性授权

授权作为服务 IAuthorizationService 实现,并在类中的服务集合 Startup 中注册。 该服务通过依赖项注入提供给页面处理程序或操作。

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

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

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

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

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

注意

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

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

编写基于资源的处理程序

为基于资源的授权编写处理程序与编写普通的要求处理程序没有太大区别。 创建自定义要求类并实现要求处理程序类。 有关创建要求类的详细信息,请参阅要求

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

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 属性可以设置为当前用户。

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

操作要求

如果要根据 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 属性来验证操作。

操作资源处理程序的挑战和禁止

本部分说明如何处理挑战和禁止操作结果,以及挑战和禁止的区别。

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

注意

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

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

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

授权方法取决于资源。 例如,只有文档的作者有权更新文档。 因此,必须先从数据存储中检索文档,然后才能进行授权评估。

属性评估发生在数据绑定之前以及执行加载文档的页面处理程序或操作之前。 出于这些原因,使用 [Authorize] 属性进行声明性授权无法满足需求。 你可以调用自定义授权方法 - 一种称为命令性授权的样式。

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

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

使用命令性授权

授权作为 IAuthorizationService 服务实现,并在类中的服务集合 Startup 中注册。 该服务通过依赖项注入提供给页面处理程序或操作。

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

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

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

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

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

注意

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

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

编写基于资源的处理程序

为基于资源的授权编写处理程序与编写普通的要求处理程序没有太大区别。 创建自定义要求类并实现要求处理程序类。 有关创建要求类的详细信息,请参阅要求

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

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 属性可以设置为当前用户。

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

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

操作要求

如果要根据 CRUD (Create、Read、Update、Delete) 操作的结果做出决策,请使用 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 属性来验证操作。

操作资源处理程序的挑战和禁止

本部分说明如何处理挑战和禁止操作结果,以及挑战和禁止的区别。

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

注意

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

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

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