Asp.net-Web-Api

OAuthBearerAuthenticationMiddleware - 發送 HTTP 標頭後,伺服器無法附加標頭

  • April 24, 2015

我一直在嘗試將一些 OWIN 中間件插入現有的 WebApi 項目。我的初創公司最初只包含以下幾行:

application.UseOAuthBearerAuthentication(newOAuthBearerAuthenticationOptions());
application.UseWebApi(config);

使用這種配置,我間歇性地,主要是在 iisreset 之後,接收到由中間件嘗試添加標頭引起的格式錯誤的響應(由 fiddler 辨識),但是在發送響應之後,這被報告為異常:

Server cannot append header after HTTP headers have been sent.

我重新編譯了 Microsoft.Owin.Security.OAuth 以添加一些額外的跟踪來顯示事情發生的順序,我得到了以下輸出:

Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware Information: 0 : : OAUTH: Authenticating...
System.Web.Http.Request: ;;http://localhost:555/entityinstance/member    
System.Web.Http.Controllers: WebHostHttpControllerTypeResolver;GetControllerTypes;: 
System.Web.Http.Controllers: WebHostHttpControllerTypeResolver;GetControllerTypes;: 
System.Web.Http.MessageHandlers: LogHandler;SendAsync;: 
System.Web.Http.Controllers: DefaultHttpControllerSelector;SelectController;Route='entityDefinitionName:member,controller:EntityInstance'
System.Web.Http.Controllers: DefaultHttpControllerSelector;SelectController;EntityInstance
System.Web.Http.Controllers: HttpControllerDescriptor;CreateController;: 
System.Web.Http.Controllers: WindsorCompositionRoot;Create;: 
System.Web.Http.Controllers: WindsorCompositionRoot;Create;Loyalty.Services.WebApi.Controllers.EntityInstanceController
System.Web.Http.Controllers: HttpControllerDescriptor;CreateController;Loyalty.Services.WebApi.Controllers.EntityInstanceController
System.Web.Http.Controllers: EntityInstanceController;ExecuteAsync;: 
System.Web.Http.Action: ApiControllerActionSelector;SelectAction;: 
System.Web.Http.Action: ApiControllerActionSelector;SelectAction;Selected action 'Get(String entityDefinitionName)'
System.Net.Http.Formatting: DefaultContentNegotiator;Negotiate;Type='HttpError', formatters=[JsonMediaTypeFormatterTracer, XmlMediaTypeFormatterTracer, FormUrlEncodedMediaTypeFormatterTracer, FormUrlEncodedMediaTypeFormatterTracer]
System.Net.Http.Formatting: JsonMediaTypeFormatter;GetPerRequestFormatterInstance;Obtaining formatter of type 'JsonMediaTypeFormatter' for type='HttpError', mediaType='application/json; charset=utf-8'
System.Net.Http.Formatting: JsonMediaTypeFormatter;GetPerRequestFormatterInstance;Will use same 'JsonMediaTypeFormatter' formatter
System.Net.Http.Formatting: DefaultContentNegotiator;Negotiate;Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'
System.Web.Http.Controllers: EntityInstanceController;ExecuteAsync;: 
System.Net.Http.Formatting: JsonMediaTypeFormatter;WriteToStreamAsync;Value='System.Web.Http.HttpError', type='HttpError', content-type='application/json; charset=utf-8'
System.Net.Http.Formatting: JsonMediaTypeFormatter;WriteToStreamAsync;: 
System.Web.Http.Request: ;;Content-type='application/json; charset=utf-8', content-length=68
System.Web.Http.Controllers: EntityInstanceController;Dispose;: 
System.Web.Http.Controllers: EntityInstanceController;Dispose;: 
Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware Information: 0 : : OAUTH: Applying Challange...
A first chance exception of type 'System.Web.HttpException' occurred in System.Web.dll

所以看起來是中間件的響應處理部分,遵循俄羅斯娃娃模型正在嘗試修改標頭,但響應已經在前一階段完成。我嘗試添加不同的階段標記來控制這種行為,但似乎沒有任何幫助。

看到那條痕跡後,令人驚訝的是,它並非一直都在發生。我用一個更簡單的實現編寫了我自己的這個中間件版本,在註冊這個之後,代替 MS,我確實開始看到每個請求的錯誤,我想知道它是否總是被拋出,但被吞了有時,或者提琴手是否沒有等待足夠長的時間才能看到它。

我目前最好的猜測是這個問題的發生是由於對 Owin 使用不同的 HttpConfiguration 而不是 WebApi 設置。不幸的是,我無法換出整個 HTTPApplication 並轉到 OWIN 鎖庫,因為委派處理程序在 OWIN 的不同上下文中執行,您無法訪問路由數據,因為這會破壞很多我們現有的基礎設施。

任何人都可以給我任何關於這裡發生的事情的指示,這是一個受支持的場景嗎?我錯過了一些明顯的東西嗎?

好的,好的,我已經解決了!

簡答

如果您使用兩個 HttpConfiguration 實例,則會出現此問題。您必須確保對呼叫application.UseWebApi(config);和 webapi 配置使用相同的 HttpConfiguration。

我猜這是因為除非您使用相同的配置,否則執行時無法知道何時準備好發送響應,因為沒有一個地方可以確定您的所有處理程序已執行。

中型答案

轉換現有 webapi 應用程序時,您通常會在 global.asaxapplication_start處理程序中擁有容器引導程序和 web api 配置註冊。遷移到 OWIN 時,您要做的第一件事就是添加一個Startup類,用於通過appbuilder. 在這種情況下,很容易遵循純 OWIN 範例並在您的新 HttpConfiguration 中新建一個新的 HttpConfiguration,startup同時使用GlobalConfiguration. 你最終會得到類似的東西:

全球突擊:

   protected void Application_Start()
   {
       var config =  GlobalConfiguration.Configuration;
       Bootstrapper.Run(config);            
       WebApiConfig.Register(config);
    }

啟動.cs:

 public void Configuration(IAppBuilder application)
           {
               var config = new HttpConfiguration();
               application.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions());
               application.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

               config.MapHttpAttributeRoutes();
               application.UseWebApi(config);   
           }

當你真正需要的是:

public void Configuration(IAppBuilder application)
       {
           var config = new HttpConfiguration();

           Bootstrapper.Run(config);

           application.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions());
           application.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

           WebApiConfig.Register(config);
           config.MapHttpAttributeRoutes();
           application.UseWebApi(config);   
       }

更長的答案

您可能會認為這是相當明顯的,如果您密切關注這些範例,您會很快發現這一點,而且您是對的。這是我在遇到問題後不久嘗試過的(我沒有編寫原始程式碼;))。不幸的是,如果您嘗試使用 WebApi 項目中使用的一些標準 web.config 進行上述操作,您將遇到許多其他問題,這些問題會出現您認為可能與您的原始問題相關的問題,但事實並非如此。

*問題:*每個請求都是 404。

*解決方案:*您需要註冊以下處理程序:

<!-- language: lang-xml -->
 <handlers accessPolicy="Read, Execute, Script">
   <remove name="WebDAV" />    
   <remove name="ExtensionlessUrlHandler-Integrated-4.0" />      
   <remove name="TRACEVerbHandler" />
   <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

問題:發送響應後無法重定向/發出 401 後404 重定向到login.aspx 。

解決方案:需要註銷 formsAuthentication 模組:

<!-- language: lang-xml -->
<modules runAllManagedModulesForAllRequests="true">
     <remove name="FormsAuthentication" />
</modules>

問題: “message”:“未找到與請求 URI ’ http://blah ’ 匹配的 HTTP 資源。”,“messageDetail”:“未找到與名為 ‘blah’ 的控制器匹配的類型。”

*解決方案:*這是一個微妙的問題。我們正在使用委託處理程序來掃描我們的控制器操作以查找Authenticate屬性。我們使用以下程式碼執行此操作:

public virtual T ScanForAttribute<T>(HttpRequestMessage request, HttpConfiguration config) where T : Attribute
       {
           var controllerSelector = new DefaultHttpControllerSelector(config);
           var descriptor = controllerSelector.SelectController(request);

           .. some other stuff
       }

現在,我們在 OWIN 下遇到的問題是controllerSelector.SelectController(在 中實現System.Web.Http)內部依賴於MS_RequestContextrequest 屬性,如果它沒有找到它,那麼它會拋出一個狀態碼為 404 的 HttpResponseException,這會導致發送 404 響應,因此上述問題。您可以通過一些技巧在 OWIN 中使用它:

public virtual T ScanForAttribute<T>(HttpRequestMessage request, HttpConfiguration config) where T : Attribute
       {
           var data = request.GetConfiguration().Routes.GetRouteData(request);
           ((HttpRequestContext) request.Properties["MS_RequestContext"]).RouteData = data;

           var controllerSelector = new DefaultHttpControllerSelector(config);
           var descriptor = controllerSelector.SelectController(request);
           .. Some other stuff
       }

*問題:*您嘗試使用以下方法解析呼叫者 IP:

if (request.Properties.ContainsKey("MS_HttpContext"))
               {
                   return ((HttpContextWrapper)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
               }

*解決方案:*您還需要檢查以下內容,它才能在 OWIN 下工作:

 if (request.Properties.ContainsKey("MS_OwinContext"))
               {
                   OwinContext owinContext = (OwinContext)request.Properties["MS_OwinContext"];
                   if (owinContext != null)
                   {
                       return owinContext.Request.RemoteIpAddress;
                   }
               } 

而且….我們現在工作得很好!如果 OWIN 有更多的跟踪輸出,解決這個問題會容易得多,但不幸的是,katana 項目中的許多中間件在跟踪方面有點安靜,希望隨著時間的推移會解決這個問題!

希望這可以幫助。

引用自:https://stackoverflow.com/questions/29824064