OAuthBearerAuthenticationMiddleware - 發送 HTTP 標頭後,伺服器無法附加標頭
我一直在嘗試將一些 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.asax
application_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 項目中的許多中間件在跟踪方面有點安靜,希望隨著時間的推移會解決這個問題!
希望這可以幫助。