2016-10-01 67 views
1

我想爲API調用實現自定義加密中間件。首先,我讀取請求正文(IOwinContext.Request.Body)和標題(加密密鑰&簽名)。然後,我解密請求主體,這給了我純JSON字符串。現在出現棘手的部分:我想將這個json寫回IOwinContextRequest.Body,所以它可以被反序列化爲對象,並在稍後作爲Controller方法的參數傳遞。這是我做的:更改OWIN Request.Body in middleware

啓動:

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== 

所以我認爲你不能這樣改變請求主體。爲了測試這一點,我已經重寫中間件這樣的:

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); 
} 

現在我認爲控制方法應該接受「原體」,而不是「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.

問題是:我的方法有什麼問題?什麼是重寫請求正文的正確方法?有沒有足夠的解決方法? 順便說一句:數據的解密測試是完美無瑕的,所以錯誤不應該出現在那裏。

編輯:在您回答/評論之前,TLS已被使用。這是另一個安全層。我不是在重新發明輪子。我正在添加一個新的。

+0

首先,我想知道爲什麼你會d這種方式不依賴於既定的TLS? – Illuminati

+0

這將在TLS之上 - 另一層。 – MatusMak

+0

對不起,我看不到任何代碼錯誤。但感覺就像你使用WebAPI來做錯事。基本上你正試圖重新發明WCF用證書信任做什麼。 – Illuminati

回答

2

我已經解決了這個問題很久以前的事,但只有在@Nkosi的意見,我張貼的解決方案。

我所做的是一種解決方法,或者說是從中間件到動作過濾器的「橋樑」。下面的代碼:

中間件

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 
    } 
} 
4

我創造了一些中間件測試的OWIN管道

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 Request.Body然後創建一個在內存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中間件之前早點把你自定義的中間件放在管道中。

希望它可以幫助