9

我正在尝试为在 WCF 中实现并托管在 Azure 上的 REST 服务实施身份验证。我正在使用 HttpModule 来处理 AuthenticationRequest、PostAuthenticationRequest 和 EndRequest 事件。如果缺少 Authorization 标头或其中包含的令牌无效,则在 EndRequest 期间,我将 Response 上的 StatusCode 设置为 401。但是,我确定 EndRequest 被调用了两次,并且在第二次调用时,响应已经具有标头设置,导致设置 StatusCode 的代码抛出异常。

我在 Init() 中添加了锁,以确保处理程序没有被注册两次;还是跑了两次。Init() 也运行了两次,表明正在创建 HttpModule 的两个实例。但是,在 VS 调试器中使用 Set Object ID 似乎表明请求实际上是不同的请求。我已经在 Fiddler 中验证了浏览器只向我的服务发出了一个请求。

如果我切换到使用 global.asax 路由而不是依赖于 WCF 服务主机配置,则处理程序只调用一次并且一切正常。

如果我将配置添加到 system.web 配置部分以及 Web.config 中的 system.webServer 配置部分,则处理程序只调用一次,一切正常。

所以我有缓解措施,但我真的不喜欢我不理解的行为。为什么处理程序被调用两次?

这是该问题的最小重现:

网络配置:

  <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模块:

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;
        }
    }
}
4

2 回答 2

8

当 ASP.net 应用程序启动时,为了最大限度地提高性能,ASP.NET Worker 进程将HttpApplication根据需要实例化尽可能多的对象。每个HttpApplication对象,还将实例化每个IHttpModule已注册的副本并调用 Init 方法!这实际上是在 IIS(或 cassini 是 VS 内置的网络服务器)下运行的 ASP.NET 进程的内部设计。可能是因为您的 ASPX 页面包含指向浏览器将尝试下载的其他资源、外部资源和 iframe、css 文件或 ASP.NET 工作进程行为的链接。

幸运的是,Global.asax 并非如此:

这里来自 MSDN

Application_Start 和 Application_End 方法是不代表 HttpApplication 事件的特殊方法。ASP.NET 在应用程序域的生命周期内调用它们一次,而不是为每个 HttpApplication 实例调用一次。

但是HTTPModule's init,在创建所有模块后,为 HttpApplication 类的每个实例调用一次方法

第一次在应用程序中请求 ASP.NET 页面或进程时,会创建一个新的 HttpApplication 实例。但是,为了最大限度地提高性能,HttpApplication 实例可能会被重复用于多个请求。

并由下图说明: 在此处输入图像描述

如果您想要保证只运行一次的代码,您可以使用Application_StartGlobal.asax设置一个标志并将其锁定在底层模块中,这对于身份验证而言不是一个好习惯!

于 2012-11-03T09:56:47.870 回答
2

抱歉,不知道为什么它会被调用两次,但是 EndRequest 最终可能会因为多种原因被调用。请求完成,请求被中止,发生了一些错误。所以我不会相信如果你到达那里,你实际上有一个 401,这可能是出于其他原因。

我只是将我的逻辑保留在 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           
            //.............         
        }
    }
于 2012-11-07T12:32:11.807 回答