OAuthBearerAuthenticationMiddleware - 服务器无法在发送 HTTP header 后附加 header
OAuthBearerAuthenticationMiddleware - Server cannot append header after HTTP headers have been sent
我一直在尝试将一些 OWIN 中间件插入现有的 WebApi 项目。我的 start-up 最初只包含以下几行:
application.UseOAuthBearerAuthentication(newOAuthBearerAuthenticationOptions());
application.UseWebApi(config);
使用此配置,我间歇性地(主要是在 iisreset 之后)收到格式错误的响应(由 fiddler 识别),这些响应是由中间件尝试添加 headers 但在响应已发送之后造成的,这被报告为异常:
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
所以看起来是中间件的响应处理部分,按照俄罗斯套娃模型试图修改headers,但是响应已经在前一阶段完成了。我尝试添加不同的阶段标记来控制这种行为,但似乎没有任何帮助。
看到那条痕迹后,令人惊讶的是,它并不是一直都在发生。我用一个更小的实现编写了我自己的这个中间件版本,在注册这个之后,代替了 MS 版本,我确实开始在每个请求上看到错误,我想知道它是否总是被抛出,但被吞下了有时或者 fiddler 是否没有等待足够长的时间来看到它。
我目前的最佳猜测是,由于对 Owin 使用与 WebApi 设置不同的 HttpConfiguration,导致出现此问题。不幸的是,我无法换出整个 HTTPApplication 并转到 OWIN 锁定库存,因为在 OWIN 的不同上下文中委派处理程序 运行 的方式,您无权访问路由数据,如这会破坏我们现有的很多基础设施。
任何人都可以给我任何关于这里发生的事情的指示,这是受支持的场景吗?我是否漏掉了一些明显的东西?
好的,好的,我已经解决了!
简答
如果您使用两个 HttpConfiguration 实例,就会遇到此问题。您必须确保对 application.UseWebApi(config);
的调用和 Webapi 配置使用相同的 HttpConfiguration。
我猜这是因为除非您使用相同的配置,否则 运行time 无法知道响应何时准备好发送,因为没有它可以查看一个地方以确定您的所有处理程序是否具有 运行.
中等大小的答案
在转换现有的 Webapi 应用程序时,您通常会在 global.asax [=20= 中注册容器 bootstrap 和 Web api 配置注册] 处理程序。迁移到 OWIN 时,您要做的第一件事就是添加一个 Startup
class,您可以使用它通过 appbuilder
配置您的 OWIN 应用程序。在这种情况下,很容易遵循纯 OWIN 示例并在 startup
中新建一个新的 HttpConfiguration,同时保留使用 GlobalConfiguration
的现有注册。你最终会得到类似的东西:
Global.asax:
protected void Application_Start()
{
var config = GlobalConfiguration.Configuration;
Bootstrapper.Run(config);
WebApiConfig.Register(config);
}
Startup.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.configs 进行上述操作,您将 运行 陷入许多其他问题,这些问题会出现您认为可能与您的原始问题相关的问题,但并非如此't.
问题:每次请求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":"No HTTP resource was found that matches the request URI 'http://blah'.",
"messageDetail": "No type was found that matches the controller named '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_RequestContext
请求 属性,如果它没有找到它然后它会抛出状态代码为 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 项目中的很多中间件在跟踪方面有点安静,希望随着时间的推移,这会得到解决!
希望这对您有所帮助。
我一直在尝试将一些 OWIN 中间件插入现有的 WebApi 项目。我的 start-up 最初只包含以下几行:
application.UseOAuthBearerAuthentication(newOAuthBearerAuthenticationOptions());
application.UseWebApi(config);
使用此配置,我间歇性地(主要是在 iisreset 之后)收到格式错误的响应(由 fiddler 识别),这些响应是由中间件尝试添加 headers 但在响应已发送之后造成的,这被报告为异常:
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
所以看起来是中间件的响应处理部分,按照俄罗斯套娃模型试图修改headers,但是响应已经在前一阶段完成了。我尝试添加不同的阶段标记来控制这种行为,但似乎没有任何帮助。
看到那条痕迹后,令人惊讶的是,它并不是一直都在发生。我用一个更小的实现编写了我自己的这个中间件版本,在注册这个之后,代替了 MS 版本,我确实开始在每个请求上看到错误,我想知道它是否总是被抛出,但被吞下了有时或者 fiddler 是否没有等待足够长的时间来看到它。
我目前的最佳猜测是,由于对 Owin 使用与 WebApi 设置不同的 HttpConfiguration,导致出现此问题。不幸的是,我无法换出整个 HTTPApplication 并转到 OWIN 锁定库存,因为在 OWIN 的不同上下文中委派处理程序 运行 的方式,您无权访问路由数据,如这会破坏我们现有的很多基础设施。
任何人都可以给我任何关于这里发生的事情的指示,这是受支持的场景吗?我是否漏掉了一些明显的东西?
好的,好的,我已经解决了!
简答
如果您使用两个 HttpConfiguration 实例,就会遇到此问题。您必须确保对 application.UseWebApi(config);
的调用和 Webapi 配置使用相同的 HttpConfiguration。
我猜这是因为除非您使用相同的配置,否则 运行time 无法知道响应何时准备好发送,因为没有它可以查看一个地方以确定您的所有处理程序是否具有 运行.
中等大小的答案
在转换现有的 Webapi 应用程序时,您通常会在 global.asax [=20= 中注册容器 bootstrap 和 Web api 配置注册] 处理程序。迁移到 OWIN 时,您要做的第一件事就是添加一个 Startup
class,您可以使用它通过 appbuilder
配置您的 OWIN 应用程序。在这种情况下,很容易遵循纯 OWIN 示例并在 startup
中新建一个新的 HttpConfiguration,同时保留使用 GlobalConfiguration
的现有注册。你最终会得到类似的东西:
Global.asax:
protected void Application_Start()
{
var config = GlobalConfiguration.Configuration;
Bootstrapper.Run(config);
WebApiConfig.Register(config);
}
Startup.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.configs 进行上述操作,您将 运行 陷入许多其他问题,这些问题会出现您认为可能与您的原始问题相关的问题,但并非如此't.
问题:每次请求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":"No HTTP resource was found that matches the request URI 'http://blah'.", "messageDetail": "No type was found that matches the controller named '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_RequestContext
请求 属性,如果它没有找到它然后它会抛出状态代码为 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 项目中的很多中间件在跟踪方面有点安静,希望随着时间的推移,这会得到解决!
希望这对您有所帮助。