【问题标题】:OAuthBearerAuthenticationMiddleware - Server cannot append header after HTTP headers have been sentOAuthBearerAuthenticationMiddleware - 发送 HTTP 标头后,服务器无法附加标头
【发布时间】:2015-07-01 16:12:14
【问题描述】:

我一直在尝试将一些 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 的不同上下文中运行,您无法访问路由数据,因为这会破坏很多我们现有的基础设施。

任何人都可以给我任何关于这里发生的事情的指示,这是一个受支持的场景吗?我错过了什么明显的东西吗?

【问题讨论】:

标签: asp.net-web-api oauth owin katana


【解决方案1】:

好的,我已经解决了!

简答

如果您使用两个 HttpConfiguration 实例,则会出现此问题。您必须确保对 application.UseWebApi(config); 的调用和 webapi 配置使用相同的 HttpConfiguration。

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

中等大小的答案

在转换现有 webapi 应用程序时,您通常会在 global.asax application_start 处理程序中拥有容器引导程序和 web api 配置注册。迁移到 OWIN 时,您要做的第一件事就是添加一个 Startup 类,您可以使用该类通过 appbuilder 配置您的 OWIN 应用程序。在这种情况下,很容易遵循纯 OWIN 示例并在 startup 中新建一个新的 HttpConfiguration,同时使用 GlobalConfiguration 保留现有注册。你最终会得到类似的东西:

全球.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.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_RequestContext 请求属性,如果它没有找到它,那么它会抛出一个状态码为的 HttpResponseException 404,导致发送 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 项目中的许多中间件在跟踪方面有点安静,希望随着时间的推移会解决这个问题!

希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 2011-09-13
    • 2011-02-21
    • 2023-03-08
    • 1970-01-01
    • 2012-01-20
    • 1970-01-01
    • 1970-01-01
    • 2011-06-02
    • 2016-03-20
    相关资源
    最近更新 更多