c# - 从授权要求处理程序中的请求正文中读取会中断路由
问题描述
我有一个用于安全性和端点的自定义处理程序。它按预期工作。
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)
{ ... }
现在,我需要检查正文的内容,所以我正在阅读它并分析内容。但是,我注意到通过此添加,不再到达端点。没有例外或任何东西,只是没有命中,就像路线不匹配一样。在调试时,我看到流已用完,因此对流进行断点并再次读取会产生一个空字符串。
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;
}
根据这个答案,我应该倒带寻找流的零点,这个建议也启用缓冲。(这里也有建议,但示例中没有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
根据策略对提交的资源( ) 执行操作。
解决这个问题的方法是实现一个IAuthorizationHandler
,它可以让你通过和检查有问题的资源。
假设我们有一个Post
类:
interface IAuthored
{
public string AuthorId { get; set; }
}
class Post : IAuthored
{
public string Title { get; set; }
public string AuthorId { get; set; }
}
我们希望只允许帖子作者对其进行编辑。
这是控制器。我添加了一个[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]
在执行到达控制器之前(在授权中间件中)评估属性。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
,它会再次读取和解析请求,并将其委托给控制器操作。
[^1]:我实际上建议不要使用这种方法,因为它将授权(这是一项业务需求)与它不应该知道的实现细节 (HTTP) 混合在一起。与以前的方法不同,它也是不可测试的。
推荐阅读
- react-native - 键盘显示固定
- python - 仅当条件匹配并分组时才对数据框列求和
- android - Android 是否对 minSdkVersion<24 的 Java 8 Iterable.forEach() 脱糖?
- r - R闪亮的异步下载xlsx
- c# - c#中分层数据的通用排序
- python - 运行数据透视函数后出现内存错误
- java - Spring - 当参数类型为 String 时,不能使用没有名称的 @ModelAttribute 吗?
- cryptography - 如何在 Linux 中启动没有包管理器的 rabbitMQ?
- eclipse - 工作模式设计器 - Spring Batch
- python - Pandas - 比较 2 列并根据优先级选择值