HTTP Handlers and HTTP Modules in ASP.NET(二)

HTTP Modules

HTTP modules are .NET components that implement the System.Web.IHttpModule interface. These components plug themselves into the ASP.NET request processing pipeline by registering themselves for certain events. Whenever those events occur, ASP.NET invokes the interested HTTP modules so that the modules can play with the request.

An HTTP module is supposed to implement the following methods of the IHttpModule interface:

Method Name Description
Init This method allows an HTTP module to register its event handlers to the events in the HttpApplication object.
Dispose This method gives HTTP module an opportunity to perform any clean up before the object gets garbage collected.

An HTTP module can register for the following events exposed by the System.Web.HttpApplication object.

Event Name Description
AcquireRequestState This event is raised when ASP.NET runtime is ready to acquire the Session state of the current HTTP request.
AuthenticateRequest This event is raised when ASP.NET runtime is ready to authenticate the identity of the user.
AuthorizeRequest This event is raised when ASP.NET runtime is ready to authorize the user for the resources user is trying to access.
BeginRequest This event is raised when ASP.NET runtime receives a new HTTP request.
Disposed This event is raised when ASP.NET completes the processing of HTTP request.
EndRequest This event is raised just before sending the response content to the client.
Error This event is raised when an unhandled exception occurs during the processing of HTTP request.
PostRequestHandlerExecute This event is raised just after HTTP handler finishes execution.
PreRequestHandlerExecute This event is raised just before ASP.NET begins executing a handler for the HTTP request. After this event, ASP.NET will forward the request to the appropriate HTTP handler.
PreSendRequestContent This event is raised just before ASP.NET sends the response contents to the client. This event allows us to change the contents before it gets delivered to the client. We can use this event to add the contents, which are common in all pages, to the page output. For example, a common menu, header or footer.
PreSendRequestHeaders This event is raised just before ASP.NET sends the HTTP response headers to the client. This event allows us to change the headers before they get delivered to the client. We can use this event to add cookies and custom data into headers.
ReleaseRequestState This event is raised after ASP.NET finishes executing all request handlers. ResolveRequestCache This event is raised to determine whether the request can be fulfilled by returning the contents from the Output Cache. This depends on how the Output Caching has been setup for your web application. UpdateRequestCache This event is raised when ASP.NET has completed processing the current HTTP request and the output contents are ready to be added to the Output Cache. This depends on how the Output Caching has been setup for your Web application.

Apart from these events, there are four more events that we can use. We can hook up to these events by implementing the methods in the global.asax file of our Web application.

These events are as follows:

  • Application_OnStart
    This event is raised when the very first request arrives to the Web application.
  • Application_OnEnd
    This event is raised just before the application is going to terminate.
  • Session_OnStart
    This event is raised for the very first request of the user's session.
  • Session_OnEnd
    This event is raised when the session is abandoned or expired.

Registering HTTP Modules in Configuration Files

Once an HTTP module is built and copied into the bin directory of our Web application or copied into the Global Assembly Cache, then we will register it in either the web.config or machine.config file.

We can use <httpModules> and <add> nodes for adding HTTP modules to our Web applications. In fact the modules are listed by using <add> nodes in between <httpModules> and </httpModules> nodes.

Since configuration settings are inheritable, the child directories inherit configuration settings of the parent directory. As a consequence, child directories might inherit some unwanted HTTP modules as part of the parent configuration; therefore, we need a way to remove those unwanted modules. We can use the <remove> node for this.

If we want to remove all of the inherited HTTP modules from our application, we can use the <clear> node.

The following is a generic example of adding an HTTP module:

<httpModules>
	<add type="classname, assemblyname" name="modulename"  />
<httpModules>

The following is a generic example of removing an HTTP module from your application.

<httpModules>
	<remove name="modulename"  />
<httpModules>

In the above XML,

  • The type attribute specifies the actual type of the HTTP module in the form of class and assembly name.
  • The name attribute specifies the friendly name for the module. This is the name that will be used by other applications for identifying the HTTP module.

Use of HTTP Modules by the ASP.NET Runtime

ASP.NET runtime uses HTTP modules for implementing some special features. The following snippet from the machine.config file shows the HTTP modules installed by the ASP.NET runtime.

<httpModules>
 <add name="OutputCache" type="System.Web.Caching.OutputCacheModule"/>
 <add name="Session" type="System.Web.SessionState.SessionStateModule"/>
 <add name="WindowsAuthentication"
   type="System.Web.Security.WindowsAuthenticationModule"/>
 <add name="FormsAuthentication"
   type="System.Web.Security.FormsAuthenticationModule"/>
 <add name="PassportAuthentication"
   type="System.Web.Security.PassportAuthenticationModule"/>
 <add name="UrlAuthorization"
   type="System.Web.Security.UrlAuthorizationModule"/>
 <add name="FileAuthorization"
   type="System.Web.Security.FileAuthorizationModule"/>
</httpModules>

All of the above HTTP modules are used by ASP.NET to provide services like authentication and authorization, session management and output caching. Since these modules have been registered in machine.config file, these modules are automatically available to all of the Web applications.

Implementing an HTTP Module for Providing Security Services

Now we will implement an HTTP module that provides security services for our Web application. Our HTTP module will basically provide a custom authentication service. It will receive authentication credentials in HTTP request and will determine whether those credentials are valid. If yes, what roles are the user associated with? Through the User.Identity object, it will associate those roles that are accessible to our Web application pages to the user's identity.

Following is the code of our HTTP module.

using System;
using System.Web;
using System.Security.Principal;

namespace SecurityModules
{
 /// <summary>
 /// Summary description for Class1.
 /// </summary>

 public class CustomAuthenticationModule : IHttpModule
 {
  public CustomAuthenticationModule()
  {
  }
  public void Init(HttpApplication r_objApplication)
  {
   // Register our event handler with Application object.
   r_objApplication.AuthenticateRequest += 
               new EventHandler(this.AuthenticateRequest) ;
  }

  public void Dispose()
  {
   // Left blank because we dont have to do anything.
  }

  private void AuthenticateRequest(object r_objSender,
                                   EventArgs r_objEventArgs)
  {
   // Authenticate user credentials, and find out user roles.
   1. HttpApplication objApp = (HttpApplication) r_objSender ;
   2. HttpContext objContext = (HttpContext) objApp.Context ;
   3. if ( (objApp.Request["userid"] == null) ||
   4.                     (objApp.Request["password"] == null) )
   5. {
   6.  objContext.Response.Write("<H1>Credentials not provided</H1>") ;
   7.  objContext.Response.End() ;
   8. }

   9. string userid = "" ;
   10. userid = objApp.Request["userid"].ToString() ;
   11. string password = "" ;
   12. password = objApp.Request["password"].ToString() ;
			
   13. string[] strRoles ;
   14. strRoles = AuthenticateAndGetRoles(userid, password) ;
   15. if ((strRoles == null) || (strRoles.GetLength(0) == 0))
   16. {
   17. objContext.Response.Write("<H1>We are sorry but we could not
       find this user id and password in our database</H1>") ;
   18. objApp.CompleteRequest() ;
   19. }

   20. GenericIdentity objIdentity = new GenericIdentity(userid,
                                             "CustomAuthentication") ;
   21. objContext.User = new GenericPrincipal(objIdentity, strRoles) ;
  }

  private string[] AuthenticateAndGetRoles(string r_strUserID,
                                                 string r_strPassword)
  {
   string[] strRoles = null ;
   if ((r_strUserID.Equals("Steve")) &&
                                  (r_strPassword.Equals("15seconds")))
   {
    strRoles = new String[1] ;
    strRoles[0] = "Administrator" ;
   }
   else if ((r_strUserID.Equals("Mansoor")) &&
                                        (r_strPassword.Equals("mas")))
   {
     strRoles = new string[1] ;
     strRoles[0] = "User" ;				
    }
    return strRoles ;
   }
  }
}

Let's explore the code.

We start with the Init function. This function plugs in our handler for the AuthenticateRequest event into the Application object's event handlers list. This will cause the Application object to call this method whenever the AuthenticationRequest event is raised.

Once our HTTP module is initialized, its AuthenticateRequest method will be called for authenticating client requests. AuthenticateRequest method is the heart of the security/authentication mechanism. In that function:

Line 1 and Line 2 extract the HttpApplication and HttpContext objects. Line 3 through Line 7 checks whether any of the userid or password is not provided to us. If this is the case, error is displayed and the request processing is terminated.

Line 9 through Line 12 extract the user id and password from the HttpRequest object.

Line 14 calls a helper function, named AuthenticateAndGetRoles. This function basically performs the authentication and determines the user role. This has been hard-coded and only two users are allowed, but we can generalize this method and add code for interacting with some user database to retrieve user roles.

Line 16 through Line 19 checks whether the user has any role assigned to it. If this is not the case that means the credentials passed to us could not be verified; therefore, these credentials are not valid. So, an error message is sent to the client and the request is completed.

Line 20 and Line 21 are very important because these lines actually inform the ASP.NET HTTP runtime about the identity of the logged-in user. Once these lines are successfully executed, our aspx pages will be able to access this information by using the User object.

Now let's see this authentication mechanism in action. Currently we are only allowing the following users to log in to our system:

  • User id = Steve, Password = 15seconds, Role = Administrator
  • User id = Mansoor, Password = mas, Role = User

Note that user id and password are case-sensitive.

First try logging-in without providing credentials. Go to http://localhost/webapp2/index.aspx and you should see the following message.

2009121814039812

Now try logging-in with the user id "Steve" and password "15seconds". Go to http://localhost/webapp2/index.aspx?userid=Steve&password=15seconds and you should see the following welcome message.

2009121814236937

Now try to log-in with the user id "Mansoor" and password "15seconds". Go to http://localhost/webapp2/index.aspx?userid=Mansoor&password=mas and you should see the following welcome page.

2009121814417734

Now try to log-in with the wrong combination of user id and password. Go to http://localhost/webapp2/index.aspx?userid=Mansoor&password=xyz and you should see the following error message.

200912181457437

This shows our security module in action. You can generalize this security module by using database-access code in the AuthenticateAndGetRoles method.

For all of this to work, we have to perform some changes in our web.config file. First of all, since we are using our own custom authentication, we don't need any other authentication mechanism. To specify this, change the <authentication> node in web.config file of webapp2 to look like this:

<authentication mode="None"/>

Similarly, don't allow anonymous users to our Web site. Add the following to web.config file:

<authorization>
 <deny users="?"/>
</authorization>

Users should at least have anonymous access to the file that they will use for providing credentials. Use the following configuration setting in the web.config file for specifying index.aspx as the only anonymously accessible file:

<location path="index.aspx">
 <system.web>
  <authorization>
   <allow users="*"/>
  </authorization>
 </system.web>
</location>

Conclusion

As you might have realized with HTTP handlers and modules, ASP.NET has put a lot of power in the hands of developers. Plug your own components into the ASP.NET request processing pipeline and enjoy the benefits.

This article should at least get you started with these components. As an exercise, you might want to go and make this sample authentication module more flexible and tune it according to your needs.

About the Author

Mansoor Ahmed Siddiqui is a software consultant and technical writer working in the United States. He has a masters degree in computer science and been involved in software development since 1997. His areas of expertise include designing and developing Web-based applications, client-server applications, and n-tier applications, with a special focus on middle-tier and Win32 programming.

He has expertise in Microsoft. NET Framework, ASP.NET, C#, Visual Studio .NET, Web Services, ADO.NET, ASP, JavaScript, eXtensible Markup Language (XML), Simple Object Access Protocol (SOAP), Visual C++, Microsoft Foundation Class Library (MFC), Active Template Library (ATL), Visual Basic 6.0, ActiveX Data Objects (ADO), COM/DCOM/COM+, Microsoft Transaction Server (MTS), Microsoft Message Queue (MSMQ), SQL Server 7.0/2000, OMG's Unified Modeling Language (UML), Rational Software Corp.'s Rational Rose, Java, Java Server Page (JSP), servlets, Enterprise JavaBeans (EJBs), Java 2 Platform Enterprise Edition (J2EE). Currently, he is working with Visual Studio 7.0 and the Microsoft .NET platform. He is an MCSD and is Brainbench certified in different languages.

Apart from technical writing, other interests include listening to music, swimming, playing cricket and pool, and hanging out with friends. He can be reached at mansoorasiddiqui@hotmail.com and ICQ 151707288.


如果给你带来帮助,欢迎微信或支付宝扫一扫,赞一下。