在中间件中更改 OWIN Request.Body
Change OWIN Request.Body in middleware
我想为 API 调用实施自定义加密中间件。起初,我阅读了请求 body (IOwinContext.Request.Body
) 和 headers (Encryption-Key & Signature)。然后,我解密请求 body,这给了我纯 json 字符串。现在是棘手的部分:我想将此 json 写回 IOwinContextRequest.Body
,以便可以将其反序列化为 object,然后作为 Controller 方法的参数传递。这是我的做法:
启动:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(EncryptionMiddleware));
...
}
}
中间件:
public class EncryptionMiddleware : OwinMiddleware
{
public EncryptionMiddleware(OwinMiddleware next) : base(next)
{
//
}
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string json = GetDecryptedJson(context);
MemoryStream stream = new MemoryStream();
stream.Write(json, 0, json.Length);
request.Headers["Content-Lenght"] = json.Lenght.ToString();
request.Body = stream;
await Next.Invoke(context);
}
}
现在,我得到的是这个错误:
System.Web.Extensions.dll!System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializePrimitiveObject()
Exception thrown: 'System.ArgumentException' in System.Web.Extensions.dll
Additional information: Invalid JSON primitive: 8yi9OH2JE0H0cwZ.
原来的IOwinContext.Request.Body
是:
8yi9OH2JE0H0cwZ/fyY5Fks4nW(...omitted...)PvL32AVRjLA==
所以我假设您不能以这种方式更改请求 body。为了测试这一点,我重写了这样的中间件:
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string requestBody = new StreamReader(request.Body).ReadToEnd();
Debug.WriteLine(requestBody); // Prints "ORIGINAL BODY"
string newBody = "\"newBody\"";
MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(newBody));
request.Headers["Content-Length"] = newBody.Length.ToString();
request.Body = memStream;
await Next.Invoke(context);
}
现在我认为 Controller 方法应该接收 "ORIGINAL BODY" 而不是 "newBody",但实际上我得到了这个错误:
System.dll!System.Diagnostics.PerformanceCounter.InitializeImpl()
Exception thrown: 'System.InvalidOperationException' in System.dll
Additional information: The requested Performance Counter is not a
custom counter, it has to be initialized as ReadOnly.
问题是:我的方法有什么问题?重写请求 body 的正确方法是什么?是否有足够的解决方法?
顺便说一句:数据的解密已经过测试并且是完美无缺的,所以错误不应该起源于那里。
编辑: 在你 answer/comment 之前,已经使用了 TLS。这是另一层安全。我不是在重新发明轮子。我要添加一个新的。
我创建了一些中间件来测试在 OWIN 管道中更改 OWIN Request.Body
public class DecryptionMiddleWare : OwinMiddleware {
private string expected;
private string decryptedString;
public DecryptionMiddleWare(OwinMiddleware next, string expected, string decryptedString)
: base(next) {
this.expected = expected;
this.decryptedString = decryptedString;
}
public async override System.Threading.Tasks.Task Invoke(IOwinContext context) {
await DecryptRequest(context);
await Next.Invoke(context);
}
private async Task DecryptRequest(IOwinContext context) {
var request = context.Request;
var requestBody = new StreamReader(request.Body).ReadToEnd();
Assert.AreEqual(expected, requestBody);
//Fake decryption code
if (expected == requestBody) {
//replace request stream to downstream handlers
var decryptedContent = new StringContent(decryptedString, Encoding.UTF8, "application/json");
var requestStream = await decryptedContent.ReadAsStreamAsync();
request.Body = requestStream;
}
}
}
public class AnotherCustomMiddleWare : OwinMiddleware {
private string expected;
private string responseContent;
public AnotherCustomMiddleWare(OwinMiddleware next, string expected, string responseContent)
: base(next) {
this.expected = expected;
this.responseContent = responseContent;
}
public async override System.Threading.Tasks.Task Invoke(IOwinContext context) {
var request = context.Request;
var requestBody = new StreamReader(request.Body).ReadToEnd();
Assert.AreEqual(expected, requestBody);
var owinResponse = context.Response;
// hold on to original stream
var owinResponseStream = owinResponse.Body;
//buffer the response stream in order to intercept downstream writes
var responseBuffer = new MemoryStream();
owinResponse.Body = responseBuffer;
await Next.Invoke(context);
if (expected == requestBody) {
owinResponse.ContentType = "text/plain";
owinResponse.StatusCode = (int)HttpStatusCode.OK;
owinResponse.ReasonPhrase = HttpStatusCode.OK.ToString();
var customResponseBody = new StringContent(responseContent);
var customResponseStream = await customResponseBody.ReadAsStreamAsync();
await customResponseStream.CopyToAsync(owinResponseStream);
owinResponse.ContentLength = customResponseStream.Length;
owinResponse.Body = owinResponseStream;
}
}
}
然后在内存中创建 OWIN 集成测试以查看数据如何通过中间件,测试是否接收到正确的数据。
[TestMethod]
public async Task Change_OWIN_Request_Body_Test() {
var encryptedContent = "Hello World";
var expectedResponse = "I am working";
using (var server = TestServer.Create<Startup1>()) {
var content = new StringContent(encryptedContent);
var response = await server.HttpClient.PostAsync("/", content);
var result = await response.Content.ReadAsStringAsync();
Assert.AreEqual(expectedResponse, result);
}
}
public class Startup1 {
public void Configuration(IAppBuilder appBuilder) {
var encryptedContent = "Hello World";
var decryptedString = "Hello OWIN";
var expectedResponse = "I am working";
appBuilder.Use<DecryptionMiddleWare>(encryptedContent, decryptedString);
appBuilder.Use<AnotherCustomMiddleWare>(decryptedString, expectedResponse);
}
}
测试通过,证明数据可以通过OWIN管道传递。
好的,接下来我想看看它是否适用于网络 api。所以创建了一个测试api控制器
public class TestController : ApiController {
[HttpPost]
public IHttpActionResult Post([FromBody]string input) {
if (input == "Hello From OWIN")
return Ok("I am working");
return NotFound();
}
}
并配置了一个新的启动来使用 web api 和自定义解密中间件。
public class Startup2 {
public void Configuration(IAppBuilder appBuilder) {
var encryptedContent = "Hello World";
var decryptedString = "\"Hello From OWIN\"";
appBuilder.Use<DecryptionMiddleWare>(encryptedContent, decryptedString);
//Configure Web API middleware
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
}
这是内存集成测试
[TestMethod]
public async Task Change_OWIN_Request_Body_To_WebApi_Test() {
var encryptedContent = "Hello World";
var expectedResponse = "\"I am working\"";
using (var server = TestServer.Create<Startup2>()) {
var content = new StringContent(encryptedContent, Encoding.UTF8, "application/json");
var response = await server.HttpClient.PostAsync("api/Test", content);
var result = await response.Content.ReadAsStringAsync();
Assert.AreEqual(expectedResponse, result);
}
}
也通过了
看看上面的示例代码,看看它是否可以让您深入了解问题中的示例哪里出了问题。
另外请记住确保在网络 api 中间件之前将自定义中间件置于管道的早期。
希望对您有所帮助
我很久以前就解决了这个问题,但是在@Nkosi 的建议下,我发布了解决方案。
我所做的是一种变通方法,或者说 "bridge" 从中间件到动作过滤器。这是代码:
中间件
public class EncryptionMiddleware : OwinMiddleware
{
public EncryptionMiddleware(OwinMiddleware next) : base(next)
{
//
}
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string requestBody = new StreamReader(request.Body).ReadToEnd();
var obj = // do your work here
System.Web.HttpContext.Current.Items[OBJECT_ITEM_KEY] = obj;
await Next.Invoke(context);
return;
}
}
过滤器
public class EncryptedParameter : ActionFilterAttribute, IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var obj = HttpContext.Current.Items[OBJECT_ITEM_KEY];
HttpContext.Current.Items.Remove(AppConfig.ITEM_DATA_KEY);
if (filterContext.ActionParameters.ContainsKey("data"))
filterContext.ActionParameters["data"] = obj;
}
}
控制器
public class MyController : Controller
{
[HttpPost]
[EncryptedParameter]
public JsonResult MyMethod(MyObject data)
{
// your logic here
}
}
我想为 API 调用实施自定义加密中间件。起初,我阅读了请求 body (IOwinContext.Request.Body
) 和 headers (Encryption-Key & Signature)。然后,我解密请求 body,这给了我纯 json 字符串。现在是棘手的部分:我想将此 json 写回 IOwinContextRequest.Body
,以便可以将其反序列化为 object,然后作为 Controller 方法的参数传递。这是我的做法:
启动:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(EncryptionMiddleware));
...
}
}
中间件:
public class EncryptionMiddleware : OwinMiddleware
{
public EncryptionMiddleware(OwinMiddleware next) : base(next)
{
//
}
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string json = GetDecryptedJson(context);
MemoryStream stream = new MemoryStream();
stream.Write(json, 0, json.Length);
request.Headers["Content-Lenght"] = json.Lenght.ToString();
request.Body = stream;
await Next.Invoke(context);
}
}
现在,我得到的是这个错误:
System.Web.Extensions.dll!System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializePrimitiveObject() Exception thrown: 'System.ArgumentException' in System.Web.Extensions.dll
Additional information: Invalid JSON primitive: 8yi9OH2JE0H0cwZ.
原来的IOwinContext.Request.Body
是:
8yi9OH2JE0H0cwZ/fyY5Fks4nW(...omitted...)PvL32AVRjLA==
所以我假设您不能以这种方式更改请求 body。为了测试这一点,我重写了这样的中间件:
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string requestBody = new StreamReader(request.Body).ReadToEnd();
Debug.WriteLine(requestBody); // Prints "ORIGINAL BODY"
string newBody = "\"newBody\"";
MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(newBody));
request.Headers["Content-Length"] = newBody.Length.ToString();
request.Body = memStream;
await Next.Invoke(context);
}
现在我认为 Controller 方法应该接收 "ORIGINAL BODY" 而不是 "newBody",但实际上我得到了这个错误:
System.dll!System.Diagnostics.PerformanceCounter.InitializeImpl() Exception thrown: 'System.InvalidOperationException' in System.dll
Additional information: The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly.
问题是:我的方法有什么问题?重写请求 body 的正确方法是什么?是否有足够的解决方法? 顺便说一句:数据的解密已经过测试并且是完美无缺的,所以错误不应该起源于那里。
编辑: 在你 answer/comment 之前,已经使用了 TLS。这是另一层安全。我不是在重新发明轮子。我要添加一个新的。
我创建了一些中间件来测试在 OWIN 管道中更改 OWIN Request.Body
public class DecryptionMiddleWare : OwinMiddleware {
private string expected;
private string decryptedString;
public DecryptionMiddleWare(OwinMiddleware next, string expected, string decryptedString)
: base(next) {
this.expected = expected;
this.decryptedString = decryptedString;
}
public async override System.Threading.Tasks.Task Invoke(IOwinContext context) {
await DecryptRequest(context);
await Next.Invoke(context);
}
private async Task DecryptRequest(IOwinContext context) {
var request = context.Request;
var requestBody = new StreamReader(request.Body).ReadToEnd();
Assert.AreEqual(expected, requestBody);
//Fake decryption code
if (expected == requestBody) {
//replace request stream to downstream handlers
var decryptedContent = new StringContent(decryptedString, Encoding.UTF8, "application/json");
var requestStream = await decryptedContent.ReadAsStreamAsync();
request.Body = requestStream;
}
}
}
public class AnotherCustomMiddleWare : OwinMiddleware {
private string expected;
private string responseContent;
public AnotherCustomMiddleWare(OwinMiddleware next, string expected, string responseContent)
: base(next) {
this.expected = expected;
this.responseContent = responseContent;
}
public async override System.Threading.Tasks.Task Invoke(IOwinContext context) {
var request = context.Request;
var requestBody = new StreamReader(request.Body).ReadToEnd();
Assert.AreEqual(expected, requestBody);
var owinResponse = context.Response;
// hold on to original stream
var owinResponseStream = owinResponse.Body;
//buffer the response stream in order to intercept downstream writes
var responseBuffer = new MemoryStream();
owinResponse.Body = responseBuffer;
await Next.Invoke(context);
if (expected == requestBody) {
owinResponse.ContentType = "text/plain";
owinResponse.StatusCode = (int)HttpStatusCode.OK;
owinResponse.ReasonPhrase = HttpStatusCode.OK.ToString();
var customResponseBody = new StringContent(responseContent);
var customResponseStream = await customResponseBody.ReadAsStreamAsync();
await customResponseStream.CopyToAsync(owinResponseStream);
owinResponse.ContentLength = customResponseStream.Length;
owinResponse.Body = owinResponseStream;
}
}
}
然后在内存中创建 OWIN 集成测试以查看数据如何通过中间件,测试是否接收到正确的数据。
[TestMethod]
public async Task Change_OWIN_Request_Body_Test() {
var encryptedContent = "Hello World";
var expectedResponse = "I am working";
using (var server = TestServer.Create<Startup1>()) {
var content = new StringContent(encryptedContent);
var response = await server.HttpClient.PostAsync("/", content);
var result = await response.Content.ReadAsStringAsync();
Assert.AreEqual(expectedResponse, result);
}
}
public class Startup1 {
public void Configuration(IAppBuilder appBuilder) {
var encryptedContent = "Hello World";
var decryptedString = "Hello OWIN";
var expectedResponse = "I am working";
appBuilder.Use<DecryptionMiddleWare>(encryptedContent, decryptedString);
appBuilder.Use<AnotherCustomMiddleWare>(decryptedString, expectedResponse);
}
}
测试通过,证明数据可以通过OWIN管道传递。
好的,接下来我想看看它是否适用于网络 api。所以创建了一个测试api控制器
public class TestController : ApiController {
[HttpPost]
public IHttpActionResult Post([FromBody]string input) {
if (input == "Hello From OWIN")
return Ok("I am working");
return NotFound();
}
}
并配置了一个新的启动来使用 web api 和自定义解密中间件。
public class Startup2 {
public void Configuration(IAppBuilder appBuilder) {
var encryptedContent = "Hello World";
var decryptedString = "\"Hello From OWIN\"";
appBuilder.Use<DecryptionMiddleWare>(encryptedContent, decryptedString);
//Configure Web API middleware
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
}
这是内存集成测试
[TestMethod]
public async Task Change_OWIN_Request_Body_To_WebApi_Test() {
var encryptedContent = "Hello World";
var expectedResponse = "\"I am working\"";
using (var server = TestServer.Create<Startup2>()) {
var content = new StringContent(encryptedContent, Encoding.UTF8, "application/json");
var response = await server.HttpClient.PostAsync("api/Test", content);
var result = await response.Content.ReadAsStringAsync();
Assert.AreEqual(expectedResponse, result);
}
}
也通过了
看看上面的示例代码,看看它是否可以让您深入了解问题中的示例哪里出了问题。
另外请记住确保在网络 api 中间件之前将自定义中间件置于管道的早期。
希望对您有所帮助
我很久以前就解决了这个问题,但是在@Nkosi 的建议下,我发布了解决方案。
我所做的是一种变通方法,或者说 "bridge" 从中间件到动作过滤器。这是代码:
中间件
public class EncryptionMiddleware : OwinMiddleware
{
public EncryptionMiddleware(OwinMiddleware next) : base(next)
{
//
}
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string requestBody = new StreamReader(request.Body).ReadToEnd();
var obj = // do your work here
System.Web.HttpContext.Current.Items[OBJECT_ITEM_KEY] = obj;
await Next.Invoke(context);
return;
}
}
过滤器
public class EncryptedParameter : ActionFilterAttribute, IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var obj = HttpContext.Current.Items[OBJECT_ITEM_KEY];
HttpContext.Current.Items.Remove(AppConfig.ITEM_DATA_KEY);
if (filterContext.ActionParameters.ContainsKey("data"))
filterContext.ActionParameters["data"] = obj;
}
}
控制器
public class MyController : Controller
{
[HttpPost]
[EncryptedParameter]
public JsonResult MyMethod(MyObject data)
{
// your logic here
}
}