我想为API调用实现自定义加密中间件。首先,我读取请求正文(IOwinContext.Request.Body)和标头(Encryption-Key&Signature)。然后,我解密请求正文,这给了我纯json字符串。现在是棘手的部分:我想将此json写回到IOwinContextRequest.Body,因此可以将其反序列化为对象,然后作为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()
      抛出异常:System.Web.Extensions.dll中的“ System.ArgumentException”

Additional information: Invalid JSON primitive: 8yi9OH2JE0H0cwZ.



原始IOwinContext.Request.Body是:

8yi9OH2JE0H0cwZ/fyY5Fks4nW(...omitted...)PvL32AVRjLA==


因此,我假定您不能以这种方式更改请求正文。为了测试这一点,我像这样重写了中间件:

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()
  抛出异常:System.dll中的“ System.InvalidOperationException”
  
  附加信息:所请求的性能计数器不是
  自定义计数器,必须将其初始化为ReadOnly。


问题是:我的方法有什么问题?重写请求正文的正确方法是什么?有足够的解决方法吗?
顺便说一句:数据解密已经过测试,并且是完美无缺的,因此不应在此处引发错误。

编辑:在您回答/评论之前,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管道传递。

好的,接下来我想看看它是否可以与Web 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);
    }
}


这也过去了。

查看上面的示例代码,看看它是否可以提供您对问题示例出了错的任何见解。

还请记住确保将自定义中间件放在Web api中间件之前的管道中。

希望能帮助到你

关于c# - 在中间件中更改OWIN Request.Body,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39812221/

10-15 16:01