2012-09-26 22 views
8

Estoy intentando implementar la autenticación para un servicio REST implementado en WCF y alojado en Azure. Estoy usando HttpModule para manejar los eventos AuthenticationRequest, PostAuthenticationRequest y EndRequest. Si falta el encabezado Authorization o si el token contenido en él no es válido, durante EndRequest estoy configurando StatusCode en la respuesta a 401. Sin embargo, he determinado que EndRequest se llama dos veces, y en la segunda llamada la respuesta ya tiene encabezados set, provocando que el código que establece StatusCode arroje una excepción.controlador HttpModule EndRequest llamado dos veces

Agregué bloqueos a Init() para garantizar que el controlador no se registrara dos veces; todavía corrió dos veces. Init() también se ejecutó dos veces, lo que indica que se crearon dos instancias del HttpModule. Sin embargo, usar Set Object ID en el depurador de VS parece indicar que las solicitudes son en realidad solicitudes diferentes. Verifiqué en Fiddler que solo se envió una solicitud a mi servicio desde el navegador.

Si cambio al uso del enrutamiento global.asax en lugar de depender de la configuración del host del servicio WCF, el controlador solo se llama una vez y todo funciona bien.

Si agrego la configuración a la sección de configuración de system.web así como a la sección de configuración de system.webServer en Web.config, el controlador solo se llama una vez y todo funciona bien.

Tengo mitigaciones, pero realmente no me gusta el comportamiento que no entiendo. ¿Por qué se llama al manejador dos veces?

Aquí es una repro mínima del problema:

Web.config:

<system.web> 
    <compilation debug="true" targetFramework="4.0" /> 
    <!--<httpModules> 
     <add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/> 
    </httpModules>--> 
    </system.web> 
    <system.serviceModel> 
    <behaviors> 
     <endpointBehaviors> 
     <behavior name="WebBehavior"> 
      <webHttp/> 
     </behavior> 
     </endpointBehaviors> 
     <serviceBehaviors> 
     <behavior> 
      <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --> 
      <serviceMetadata httpGetEnabled="true" /> 
      <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> 
      <serviceDebug includeExceptionDetailInFaults="true"/> 
     </behavior> 
     </serviceBehaviors> 
    </behaviors> 
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" /> 
    <services> 
     <service name="TestWCFRole.Service1"> 
     <endpoint binding="webHttpBinding" name="RestEndpoint" contract="TestWCFRole.IService1" bindingConfiguration="HttpSecurityBinding" behaviorConfiguration="WebBehavior"/> 
     <host> 
      <baseAddresses> 
      <add baseAddress="http://localhost/" /> 
      </baseAddresses> 
     </host> 
     </service> 
    </services> 
    <standardEndpoints> 
     <webHttpEndpoint> 
     <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/> 
     </webHttpEndpoint> 
    </standardEndpoints> 
    <bindings> 
     <webHttpBinding> 
     <binding name="HttpSecurityBinding" > 
      <security mode="None" /> 
     </binding> 
     </webHttpBinding> 
    </bindings> 
    </system.serviceModel> 
    <system.webServer> 
    <modules runAllManagedModulesForAllRequests="true"> 
     <add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/> 
    </modules> 
    <directoryBrowse enabled="true"/> 
    </system.webServer> 

Http módulo:

using System; 
using System.Web; 

namespace TestWCFRole 
{ 
    public class AuthModule : IHttpModule 
    { 
     /// <summary> 
     /// You will need to configure this module in the web.config file of your 
     /// web and register it with IIS before being able to use it. For more information 
     /// see the following link: http://go.microsoft.com/?linkid=8101007 
     /// </summary> 
     #region IHttpModule Members 

     public void Dispose() 
     { 
      //clean-up code here. 
     } 

     public void Init(HttpApplication context) 
     { 
      // Below is an example of how you can handle LogRequest event and provide 
      // custom logging implementation for it 
      context.EndRequest += new EventHandler(OnEndRequest); 
     } 

     #endregion 

     public void OnEndRequest(Object source, EventArgs e) 
     { 
      HttpContext.Current.Response.StatusCode = 401; 
     } 
    } 
} 
+0

¿Utiliza UrlRewrite en su aplicación? Parece que causa que EndRequest se dispare dos veces. –

+0

¿Se puede habilitar en segundo plano? No tengo el UrlRewriteModule en mi web.config. –

+0

No lo creo. Esto es realmente extraño, porque no puedo decir que todo mi problema sea causado por UrlRewriteModule. Pero algunos de ellos son –

Respuesta

2

No tengo ni idea de por qué se podría llamar dos veces, sin embargo EndRequest puede ser llamado por múltiples razones. solicitud finalizada, solicitud cancelada, error ocurrido. Así que no pondría mi confianza en suponer que si llegas allí, en realidad tienes un 401, podría ser por otras razones.

yo sigo mi lógica en la tubería AuthenticateRequest:

public class AuthenticationModule : IHttpModule 
    { 
     public void Dispose() { } 

     public void Init(HttpApplication context) 
     { 
      context.AuthenticateRequest += Authenticate; 
     } 

     public static void Authenticate(object sender, EventArgs e) 
     { 
      // authentication logic here    
      //............. 

      if (authenticated) { 
       HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(myUser, myRoles); 
      } 

      // failure logic here   
      //.............   
     } 
    } 
+0

Sí, no entiendo por qué no me di cuenta de cómo debería abordarlo antes (supongo que debo haber pensado que la muestra que estaba usando estaba haciendo "bien"), pero finalmente cambié la lógica a AuthenticateRequest , Secciones PostAuthenticateRequest y AuthorizeRequest. Desafortunadamente, esto no responde la pregunta de por qué el controlador se comporta de esta manera. –

7

Cuando una aplicación ASP.net se pone en marcha, para maximizar el rendimiento el proceso de trabajo de ASP.NET instanciará tantos objetos HttpApplication como necesite. Cada objeto HttpApplication, también instanciará una copia de cada IHttpModule que esté registrada y llamará al método Init. Eso es realmente un diseño interno del proceso ASP.NET que se ejecuta bajo IIS (o cassini, que VS está integrado en el servidor web). Puede ser porque su página ASPX tiene enlaces a otros recursos que su navegador intentará descargar, un recurso externo y un iframe, un archivo css o tal vez el comportamiento del Proceso de trabajo ASP.NET.

Por suerte no es el caso de Global.asax:

Here's from MSDN:

Los métodos Application_Start y Application_End son métodos especiales que no representan eventos HttpApplication. ASP.NET los llama una vez durante la vida útil del dominio de la aplicación, no para cada instancia de HttpApplication de .

Sin embargo HTTPModule's init método se llama una vez para cada instancia de la clase HttpApplication después de que todos los módulos se han creado

La primera vez que una página ASP.NET o se solicita proceso en una aplicación , un nuevo instancia de HttpApplication se crea.Sin embargo, para maximizar el rendimiento al , las instancias de HttpApplication pueden reutilizarse para solicitudes múltiples .

Y ilustra en el siguiente diagrama: enter image description here

Si desea código que está garantizado para funcionar sólo una vez, puede utilizar Application_Start del Global.asax o establecer un indicador y bloquearlo en el módulo subyacente que ¡No creo que sea una buena práctica por el bien de la Autenticación!

+0

Me gustaría que la configuración 'runAllManagedModulesForAllRequests =" true "' haga que se ejecute el módulo personalizado para cada solicitud, incluidos los archivos estáticos (JS/CSS/Images). Así que solo dos veces para una sola carga de página parece demasiado pocas para mí con esa configuración. – astaykov

+0

No necesito código para ejecutar una sola vez; el problema es que veo dos EndRequests para una sola solicitud al servicio. No debe ser JS/CSS/Images, porque este es un servicio, no una página web. Puedo ver en Fiddler que solo se está haciendo una sola solicitud. La pregunta es por qué veo una sola solicitud a través de Fiddler pero dos solicitudes en el módulo, y por qué este problema desaparece si duplico la configuración en el web.config. –

+0

Normalmente no es necesario duplicar la configuración; si está utilizando IIS7 +, la configuración debe establecerse en la sección 'system.webServer'. ¿El método 'Init' del Módulo se llama una vez mientras que el' EndRequest' se llama dos veces para una única solicitud? porque 'EndRequest' no se puede llamar dos veces por solicitud! Sin embargo, me doy cuenta de que está habilitando 'aspNetCompatibilityEnabled' en su configuración de host WCF. ¿Con qué servidor web está probando? IIS? IIS Express? ¿Servidor web VS? ¿Cómo hospedas tu WCF en IIS? Un 'WebServiceHost'? 'archivo .svc'? ¿Puedes publicar el 'HttpContext.Current.Request.Path' en EndRequest? –

Cuestiones relacionadas