OAuthBearerAuthenticationMiddleware - 服务器无法在HTTP头已发送后附加标头。

9

我一直在尝试将一些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的位置后,确实开始在每个请求上看到错误。我想知道它是否总是被抛出,但偶尔会被忽略,或者Fiddler没有等待足够长的时间才能看到它。
我目前最好的猜测是,由于在Owin和WebApi设置中使用了不同的HttpConfiguration,因此导致了这个问题。不幸的是,由于委托处理程序在OWIN中在不同的上下文中运行,您无法访问路由数据,因此无法完全交换HTTPApplication并转向OWIN,否则将破坏我们现有基础架构中的很多内容。
是否有人可以给我一些指针,说明这里发生了什么情况? 这是受支持的场景吗? 我是否忽略了一些明显的东西?

我也遇到了这个问题。 - GoatInTheMachine
可能https://dev59.com/mILba4cB1Zd3GeqPjMn8可以解决你的问题? - Stephen Reindl
1个回答

11

好的,我已经解决了!

简短回答

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

我猜测造成这个问题的原因是,除非您使用相同的配置,否则运行时无法确定响应何时准备好发送,因为它没有单一的位置可以查看以确定是否运行了所有处理程序。

中等回答

当转换现有的Web API应用程序时,通常会在global.asax application_start处理程序中进行容器引导和Web API配置注册。当移动到OWIN时,您将首先添加一个Startup类,您将使用该类通过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.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请求属性,如果找不到它,就会抛出带有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项目中的许多中间件在跟踪方面都比较安静,希望随着时间的推移能够得到解决!

希望这可以帮助您。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接