asp.net-web-api – 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(con​​fig)的调用使用相同的HttpConfiguration;并为您的webapi配置.

我猜这是因为除非你使用相同的配置,否则运行时无法知道响应何时准备发送,因为没有一个地方它可以确定是否所有你的处理程序已经运行.

中等答案

转换现有的webapi应用程序时,通常会在global.asax application_start处理程序中使用容器引导程序和web api配置注册.迁移到OWIN时,您要做的第一件事就是添加一个Startup类,您可以通过appbuilder配置您的OWIN应用程序.在这种情况下,很容易遵循纯OWIN示例并在启动时新建一个新的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来尝试上述内容,您将遇到许多其他问题,这些问题会导致您认为可能与原始问题相关的问题,但事实并非如此.

问题:每个请求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>

问题:发出响应后无法重定向/ 404在发出401后重定向到login.aspx.

解决方案:您需要取消注册formsAuthentication模块:

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

问题:“消息”:“找不到与请求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_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项目中的很多中间件在跟踪方面有点安静,希望这是随着时间的推移会得到解决的问题!

希望这可以帮助.

转载注明原文:asp.net-web-api – OAuthBearerAuthenticationMiddleware – 在发送HTTP标头后,服务器无法附加标头 - 代码日志