【问题标题】:Authorization policy via attribute before data binding in web apiWeb api中数据绑定之前通过属性的授权策略
【发布时间】:2019-07-17 11:25:17
【问题描述】:

我正在努力寻找一个很好的解决方案来进行自定义授权检查,而不必一遍又一遍地手动重复授权检查。

为了说明,假设我有以下 .net 核心 web api 设置,它有两个端点,一个用于 GET,一个用于 POST。我想检查(可能针对 db)用户是否有权查看资源,或有权创建资源。

这就是文档中提到的resource based authorization 看起来像这样:

[Authorize]
[ApiVersion ("1.0")]
[Route ("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ResourcesController : ControllerBase {
    private readonly IAuthorizationService _authorizationService;

    //..constructor DI

    [HttpGet ("{resourceId}")]
    public ActionResult<Resource> Get (Guid resourceId) {
        var authorizationCheck = await _authorizationService.AuthorizeAsync (User, resourceId, ServiceOperations.Read);
        if (!authorizationCheck.Succeeded) {
            return Forbid ();
        }

        return Ok (ResourceRep.Get (resourceId));
    }

    [HttpPost]
    public ActionResult<Resource> Post ([FromBody] Resource resource) {
        var authorizationCheck = await _authorizationService.AuthorizeAsync (User, null, ServiceOperations.Write);
        if (!authorizationCheck.Succeeded) {
            return Forbid ();
        }

        return Ok (ResourceRep.Create (resource));
    }
}

现在想象ServiceOperations 枚举有很长的受支持操作列表,或者有 100 个不同的端点,我将不得不在任何地方进行相同的检查,或者更糟糕的是,可能会忘记在我应该有的地方添加检查加了一张支票。而且在单元测试中也没有简单的方法来解决这个问题。

我曾想过使用属性,但正如文档所述:

属性评估发生在数据绑定之前和加载文档的页面处理程序或操作执行之前。由于这些原因,带有 [Authorize] 属性的声明式授权是不够的。相反,您可以调用自定义授权方法 — 一种称为命令式授权的样式。

因此,当检查本身需要不可用的参数(resourceId)时,我似乎无法使用授权策略并使用授权属性(很容易对它们进行单元测试)来装饰方法。

所以对于问题本身: 您如何一般使用命令式(基于资源的)授权而不必重复自己(这很容易出错)。我希望有如下属性:

[HttpGet ("{resourceId}")]
[AuthorizeOperation(Operation = ServiceOperations.Read, Resource=resourceId)]
public ActionResult<Resource> Get (Guid resourceId) {..}

[AuthorizeOperation(Operation = ServiceOperations.Write)]
[HttpPost]
public ActionResult<Resource> Post ([FromBody] Resource resource) {..}

【问题讨论】:

  • AuthorizeOperation 属性的实际用途是什么? [Authorize] 和 [AllowAnonymous] 在控制器和操作上可以互换,因此您可以通过这种方式微调您的需求。

标签: asp.net-core authorization


【解决方案1】:

您可以在基于策略的授权中使用AuthorizationHandler 来实现它,并结合专门创建的注入服务来确定操作-资源配对。

为此,首先在Startup.ConfigureServices 中设置策略:

services.AddAuthorization(options =>
{
    options.AddPolicy("OperationResource", policy => policy.Requirements.Add( new OperationResourceRequirement() ));
});
services.AddScoped<IAuthorizationHandler, UserResourceHandler>();
services.AddScoped<IOperationResourceService, OperationResourceService>();

接下来创建OperationResourceHandler

public class OperationResourceHandler: AuthorizationHandler<OperationResourceRequirement>
{
    readonly IOperationResourceService _operationResourceService;

    public OperationResourceHandler(IOperationResourceService o)
    {
        _operationResourceService = o;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authHandlerContext, OperationResourceRequirement requirement)
    {
        if (context.Resource is AuthorizationFilterContext filterContext)
        {
            var area = (filterContext.RouteData.Values["area"] as string)?.ToLower();
            var controller = (filterContext.RouteData.Values["controller"] as string)?.ToLower();
            var action = (filterContext.RouteData.Values["action"] as string)?.ToLower();
            var id = (filterContext.RouteData.Values["id"] as string)?.ToLower();
            if (_operationResourceService.IsAuthorize(area, controller, action, id))
            {
                context.Succeed(requirement);
            }               
        }            
    }
}

OperationResourceRequirement 可以是一个空类:

public class OperationResourceRequirement : IAuthorizationRequirement { }

诀窍是,我们不是在属性中指定动作的操作,而是在其他地方指定它,例如在数据库、appsettings.json、某些配置文件或硬编码中。

这是一个从配置文件中获取操作资源对的示例:

public class OperationResourceService : IOperationResourceService
{
    readonly IConfiguration _config;
    readonly IHttpContextAccessor _accessor;
    readonly UserManager<AppUser> _userManager;

    public class OpeartionResourceService(IConfiguration c, IHttpContextAccessor a, UserManager<AppUser> u) 
    {
        _config = c;
        _accessor = a;
        _userManager = u;
    }

    public bool IsAuthorize(string area, string controller, string action, string id)
    {
        var operationConfig = _config.GetValue<string>($"OperationSetting:{area}:{controller}:{action}"); //assuming we have the setting in appsettings.json
        var appUser = await _userManager.GetUserAsync(_accessor.HttpContext.User);
        //all of needed data are available now, do the logic of authorization
        return result;
    } 
}

请注意,要使IHttpContextAccessor 可注入,请在Startup.ConfigurationServices 方法体中添加services.AddHttpContextAccessor()

全部完成后,对操作使用策略:

[HttpGet ("{resourceId}")]
[Authorize(Policy = "OperationResource")]
public ActionResult<Resource> Get (Guid resourceId) {..}

每个操作的授权策略都可以相同。

【讨论】:

    猜你喜欢
    • 2018-07-21
    • 1970-01-01
    • 1970-01-01
    • 2017-08-29
    • 2020-04-11
    • 1970-01-01
    • 2019-04-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多