.NET Core中能否两次读取Request.Body?签名验证遇模型绑定问题
请求体签名验证过滤器导致模型绑定失败的解决方案
问题原因
你的验证过滤器读取Request.Body后,尝试通过Position = 0重置流位置,但ASP.NET Core默认的HttpRequestStream是不可重读的单向流,不支持查找操作,所以重置位置无效。后续模型绑定读取请求体时,流已处于末尾,无法获取内容,导致MyModel实例属性全部为null。
解决方案
方案1:启用请求体缓冲(推荐)
调用Request.EnableBuffering()方法,让ASP.NET Core将请求体缓存到内存或临时文件中,使流支持重读和位置重置。修改过滤器代码如下:
public class Verify : ActionFilter, IAsyncAuthorizationFilter { public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { var request = context.HttpContext.Request; request.EnableBuffering(); // 启用缓冲,使流可重读 var signature = request.Headers["x-signature"].FirstOrDefault(); if (string.IsNullOrEmpty(signature)) { context.Result = new UnauthorizedResult(); return; } var requestBody = await new StreamReader(request.Body).ReadToEndAsync(); request.Body.Position = 0; // 现在重置位置有效,后续模型绑定可正常读取 var key = "my-secure-key"; if (EncryptTheBody(requestBody, key) != signature) { context.Result = new UnauthorizedResult(); return; } } }
方案2:替换为内存流
将读取后的请求体内容存入MemoryStream,并替换原来的Request.Body,确保后续读取操作能获取到内容:
public class Verify : ActionFilter, IAsyncAuthorizationFilter { public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { var request = context.HttpContext.Request; var signature = request.Headers["x-signature"].FirstOrDefault(); if (string.IsNullOrEmpty(signature)) { context.Result = new UnauthorizedResult(); return; } // 将请求体复制到内存流 using var memoryStream = new MemoryStream(); await request.Body.CopyToAsync(memoryStream); memoryStream.Position = 0; // 从内存流读取内容用于验证 var requestBody = await new StreamReader(memoryStream).ReadToEndAsync(); memoryStream.Position = 0; // 重置内存流位置 // 替换原请求体为内存流 request.Body = memoryStream; var key = "my-secure-key"; if (EncryptTheBody(requestBody, key) != signature) { context.Result = new UnauthorizedResult(); return; } } }
更优实现建议
- 避免硬编码密钥:将
my-secure-key存入配置文件(如appsettings.json),通过依赖注入IConfiguration读取,提升安全性和可维护性。 - 解耦验证逻辑:将签名验证逻辑封装为独立服务(如
ISignatureValidator),通过依赖注入注入到过滤器中,方便单元测试和逻辑复用。 - 使用ASP.NET Core授权模型:考虑实现
IAuthorizationHandler和自定义授权策略,替代ActionFilter,更符合框架的授权规范,扩展性更强。
示例:封装验证服务的实现
// 定义验证服务接口 public interface ISignatureValidator { bool Validate(string requestBody, string signature, string secretKey); } // 实现验证服务 public class SignatureValidator : ISignatureValidator { public bool Validate(string requestBody, string signature, string secretKey) { // 替换为你的加密验证逻辑 var computedSignature = EncryptTheBody(requestBody, secretKey); return computedSignature == signature; } private string EncryptTheBody(string body, string key) { // 你的加密实现 } } // 使用TypeFilterAttribute实现依赖注入的过滤器 public class VerifyAttribute : TypeFilterAttribute { public VerifyAttribute() : base(typeof(VerifyAuthorizationFilter)) { } private class VerifyAuthorizationFilter : IAsyncAuthorizationFilter { private readonly ISignatureValidator _signatureValidator; private readonly IConfiguration _configuration; public VerifyAuthorizationFilter(ISignatureValidator signatureValidator, IConfiguration configuration) { _signatureValidator = signatureValidator; _configuration = configuration; } public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { var request = context.HttpContext.Request; request.EnableBuffering(); var signature = request.Headers["x-signature"].FirstOrDefault(); if (string.IsNullOrEmpty(signature)) { context.Result = new UnauthorizedResult(); return; } var requestBody = await new StreamReader(request.Body).ReadToEndAsync(); request.Body.Position = 0; var secretKey = _configuration["SignatureSettings:SecretKey"]; if (!_signatureValidator.Validate(requestBody, signature, secretKey)) { context.Result = new UnauthorizedResult(); return; } } } }
最后在Program.cs中注册服务:
builder.Services.AddScoped<ISignatureValidator, SignatureValidator>();
内容的提问来源于stack exchange,提问作者jb.
相关产品推荐
相关产品推荐

