c# - 从授权要求处理程序中的请求正文中读取会破坏路由

标签 c# asp.net-core routes stream asp.net-core-3.1

我有一个用于安全性的自定义处理程序和一个端点。它按预期工作。

protected override Task HandleRequirementAsync(
    AuthorizationHandlerContext context,
    AwoRequirement requirement)
{
    HttpRequest request = Accessor.HttpContext.Request;
    string awo = request.Headers
        .SingleOrDefault(a => a.Key == "awo").Value.ToString();
    ...
    bools authorized = ...;

    if (authorized) context.Succeed(requirement);
    else context.Fail();
    return Task.CompletedTask;
}

[Authorize(Policy = "SomePolicy"), HttpPost("something")]
public async Task<IActionResult> Something([FromBody] Thing dto)
{ ... }

现在,我需要检查正文的内容,因此我正在读入并分析内容。但是,我注意到添加此内容后,不再达到终点。没有异常(exception)或任何事情,只是没有命中,就像路线不匹配一样。在调试时,我看到流已用完,因此对流进行断点并再次读取会产生一个空字符串。

protected override Task HandleRequirementAsync( ... )
{
    HttpRequest request = Accessor.HttpContext.Request;
    ...
    using StreamReader stream = new StreamReader(Accessor.HttpContext.Request.Body);
    string body = stream.ReadToEndAsync().Result;
    Thing thing = JsonSerializer.Deserialize<Thing>(body);
    if (thing.IsBad())
      authorized &= fail;
    ...
    return Task.CompletedTask;
}

根据this answer我应该倒带寻找流的零点,this one建议也启用缓冲。 (还有建议here,但示例中没有await,这是我的系统所必需的,所以我无法正确尝试。)基于此,我得出了以下结论。

protected override Task HandleRequirementAsync( ... )
{
    HttpRequest request = Accessor.HttpContext.Request;
    ...
    Accessor.HttpContext.Request.EnableBuffering();
    using StreamReader stream 
      = new StreamReader(Accessor.HttpContext.Request.Body);
    string body = stream.ReadToEndAsync().Result;
    Thing thing = JsonSerializer.Deserialize<Thing>(body);
    if (thing.IsBad())
      authorized &= fail;
    ...
    return Task.CompletedTask;
}

现在,返回并重新运行代码确实会再次从流中读取。但是,仍然找不到端点,就像添加上述内容之前一样。不过,如果我从流中删除读数,就可以达到这个目的,所以我感觉我仍然以某种方式影响正文读数。

最佳答案

我猜您需要检查是否允许用户根据策略对提交的资源执行操作 ( Thing )。

The way to go about this是实现IAuthorizationHandler ,它可以让您通过并检查有问题的资源。

假设我们有 Post类:

interface IAuthored
{
    public string AuthorId { get; set; }
}

class Post : IAuthored
{
    public string Title { get; set; }
    public string AuthorId { get; set; }
}

我们只想允许帖子作者对其进行编辑。

这是 Controller 。我添加了 [Authorize]仅允许经过身份验证的用户进入。

public class PostController : ControllerBase
{
    private AppDbContext _dbContext;
    private IAuthorizationService _authorizationService;

    public PostController(IAuthorizationService authorizationService, AppDbContext dbContext)
    {
        _authorizationService = authorizationService;
        _dbContext = dbContext;
    }

    [Authorize] // this wouldn't work! [*]
    [HttpPatch("{id}")]
    public async Task<ActionResult> EditPost(string id)
    {
        var post = await _dbContext.Set<Post>().FindAsync(id);

        // oops! any authenticated user can edit this post.

        post.Title = "asd";
        await _dbContext.SaveChangesAsync();

        return Ok();
    }
}

通常,使用简单的策略,我们会用 [Authorize("my_policy")] 注释该操作。但它在这里不起作用,因为 [Authorize]在执行到达 Controller 之前(在授权中间件中)评估属性。 ASP.NET Core(或您)无法知道正在处理哪个资源[*]。

因此我们需要强制授权该操作。我们定义一项要求,以及执行该要求的政策。

// just a marker class
class AuthorRequirement : IAuthorizationRequirement
{
}

services.AddAuthorization(
    options => {
        options.AddPolicy("editor", builder => builder.Requirements.Add(new AuthorRequirement()));
    }
);

然后为此要求实现一个处理程序。我们可以对 AuthorizationHandler<TRequirement, TResource> 进行子类化或AuthorizationHandler<TRequirement> 。我选择授权所有实现 IAuthored 的类界面。

class AuthorRequirementHandler : AuthorizationHandler<AuthorRequirement, IAuthored> // for a specific
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorRequirement requirement, IAuthored resource)
    {
        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (resource.AuthorId == userId)
        {
            context.Succeed(requirement);
        }

        // ... let other handlers take a stab at this
        return Task.CompletedTask;
    }
}

这可行,但我们被迫在操作内强制处理授权。

[*]:如果我们可以在资源到达端点之前推断出资源,我们就可以短路整个操作。

让我们创建一个扩展方法,让我们多次读取请求正文,并启用请求缓冲。

internal static class HttpRequestExtensions {
    public static async Task<T> ReadAsJsonAsync<T>(this HttpRequest request, JsonSerializerOptions options = null)
    {
        request.Body.Position = 0;
        var result = await request.ReadFromJsonAsync<T>(options);
        // reset the position again to let endpoint middleware read it
        request.Body.Position = 0;
        return result;
    }
}


app.Use((context, next) => {
    context.Request.EnableBuffering(1_000_000);
    return next();
});

app.UseAuthorization();

现在我们可以重写处理程序来读取正文并“以声明方式”执行授权 [^1]。

class AuthorRequirementHandler : AuthorizationHandler<AuthorRequirement>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public AuthorRequirementHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorRequirement requirement)
    {
        var endpoint = _httpContextAccessor.HttpContext.GetEndpoint();
        var action  = endpoint.Metadata.OfType<ControllerActionDescriptor>().FirstOrDefault();
        // is the action is expecting a post DTO
        if (!action.Parameters.Any(p => p.ParameterType == typeof(Post)))
        {
            return;
        }
        
        var post = await _httpContextAccessor!.HttpContext!.Request.ReadAsJsonAsync<Post>();
        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (post.AuthorId == userId)
        {
            context.Succeed(requirement);
        }
    }
}

一旦用户获得授权,请求就会沿着中间件链到达 EndpointMiddleware ,它再次读取并解析请求,并将其委托(delegate)给 Controller 操作。


[^1]:我实际上建议反对这种方法,因为它混合了授权(这是一项业务需求)和它不应该知道的实现细节(HTTP)。与之前的方法不同,它也是不可测试的。

关于c# - 从授权要求处理程序中的请求正文中读取会破坏路由,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68723040/

相关文章:

C# Regex Replace 附加换行符

c# - 使用 iTextSharp 获取复选框的导出值

c# - 无法连续发送三个或更多数据包

c# - 如何在文本文件中添加标题

c# - .Net Core JwtBearer 身份验证不拒绝过期的 token

asp.net - 如何在 ASP.NET Core 应用程序中使用 HttpClient

asp.net-core - 在 EF Core 3.0 与 2.2 中执行存储过程

ruby-on-rails - Ruby on Rails - 名称错误 : uninitialized constant UsersController at/admin/users

javascript - EmberJS路由复用: edit and create new model

ruby-on-rails - 布线 Rack 的问题