我有一个用于安全性的自定义处理程序和一个端点。它按预期工作。
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/