Asp.net 核心 web-api 基于 http 操作的授权

Asp.net core web-api http action based authorization

我正在尝试向 asp.net 核心 api 添加两个策略。

  1. ReadOnly - 只允许 get 调用
  2. AppUser - 允许获取、放置、post、删除

//// 政策在应用程序设置中 json

Policies.json
{
    "name": "ReadOnly"
    "ids":["user1", "user2"],
}
,
{
    "name": "AppUser"
    "ids":["user3", "user4"]
}

//// 要求

public class IdRequirement : IAuthorizationRequirement
{
    public string Name { get; set; }
    public List<string> Ids { get; set; } = new List<string>();

    public IdRequirement(string name, List<string> ids)
    {
        Name = name;
        Ids = ids;
    }
}

//// 处理程序

public class IdHandler : AuthorizationHandler<IdRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IdRequirement requirement)
    {
        if (context.User.Identity.IsAuthenticated) 
        {
            string currentUserid = GetId(context.User);

            if (requirement.Ids != null && requirement.Ids.Any(x => x == currentUserid))
            {
                context.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }
}

//// 在服务中

services.AddAuthorization(options =>
{
    var reqList = configuration.GetSection("Policies").Get<List<IdRequirement>>();

    //// app users must have readonly access by default. ReadOnly is the default policy.
    //// not sure this is the correct way?
    
    var appUserReq = reqList.Find(x => x.Name.Equals("AppUser", StringComparison.Ordinal));
    var readOnlyReq = reqList.Find(x => x.Name.Equals("ReadOnly", StringComparison.Ordinal));

    readOnlyReq.Ids = readOnlyReq.Ids.Union(appUserReq.ids).ToList();

    reqList.ForEach(requirement =>
    {
        options.AddPolicy(requirement.Name, policy =>
        {
            policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
            policy.Requirements.Add(requirement);

        });
    });
});

//// 在配置中

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers().RequireAuthorization("ReadOnly");
});

//// 控制器

public class TestController : BaseController
{
    // GET: userid
    [HttpGet("testget")]
    public async Task<IActionResult> GetSomething()
    {
        return Ok();
    }

    [Authorize(Policy = "AppUser")]
    [HttpPost("testpost")]
    public async Task<IActionResult> PostSomething()
    {
        return Ok();
    }
}

如果我调用testget,它只会授权只读需求。这对我来说是正确的。

如果我调用testpost,它会先授权appuser需求,然后再调用readonly需求。

我不想在使用 [Authorize(Policy = "AppUser")] 时触发只读要求

  1. 我是漏掉了还是误用了概念?
  2. 这是默认行为吗?

我引用了这篇文章 https://github.com/blowdart/AspNetAuthorizationWorkshop

你没有遗漏任何东西。将 [Authorize(Policy = "AppUser")] 放在操作上时,它将首先通过此策略 AppUser 进行身份验证。如果不成功,它将重定向到 AccessDeniedPath。这时会经过端点,端点中作为全局授权的RequireAuthorization会再次触发。如果不配置AccessDeniedPath,会return404.

最终借助这个 的帮助,我取得了如下成就。确保您不再需要在控制器中添加 Authorize 属性。如果您想要特定控制器的任何自定义设置,可以修改 RequireAuthorizationForHttpMethods

var mutatingHttpMethods = new HashSet<HttpMethod>()
{
    HttpMethod.Post,
    HttpMethod.Put,
    HttpMethod.Delete
};

var getHttpMethods = new HashSet<HttpMethod>()
{
    HttpMethod.Get
};

endpoints
    .MapControllers()
    .RequireAuthorizationForHttpMethods(httpMethods => httpMethods.Any(httpMethod => getHttpMethods.Contains(httpMethod)), "ReadOnly")
    .RequireAuthorizationForHttpMethods(httpMethods => httpMethods.Any(httpMethod => mutatingHttpMethods.Contains(httpMethod)), "AppUser");