Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Asp Net 2.0 Security Membership And Role Management

.pdf
Скачиваний:
55
Добавлен:
17.08.2013
Размер:
12.33 Mб
Скачать

Chapter 6

Figure 6-1

The Application Configuration dialog box, shown in Figure 6-2, in IIS6 now has two sections: one where you can adjust one-to-one associations of file types to specific ISAPI extensions and a new section at the bottom where you can set up one or more wildcard mappings.

Unless you have a photographic memory, you probably don’t remember the full path to the ASP.NET ISAPI extension. So, before configuring wildcard mappings, it is helpful to select one of the preexisting mappings (for example, the .aspx mapping) and click the Edit button. The Add/Edit Application Extension Mapping dialog box, shown in Figure 6-3, conveniently holds the full path to the ASP.NET ISAPI extension in the Executable text box.

262

Integrating ASP.NET Security with Classic ASP

Figure 6-2

Figure 6-3

263

Chapter 6

Copy the path and then cancel out of the dialog box. Now you can click the Insert button in the bottom half of the Application Configuration dialog box to open the dialog box for configuring wildcard extension mappings (Figure 6-4.) Paste in the full path to the ASP.NET ISAPI extension into the Executable text box.

Figure 6-4

Close out of all of the dialog boxes by clicking OK. You have now configured an application inside of IIS6 that will forward all requests initially to the ASP.NET 2.0 ISAPI extension. Due to the new functionality of the DefaultHttpHandler inside of ASP.NET 2.0, these requests will handed off to IIS6 for execution by the appropriate extension or internal runtime logic. After the appropriate extension or IIS6 had completed its processing, ASP.NET 2.0 will have the chance to perform some postprocessing, after which the request will complete.

For now just a simple ASP page is used:

<%

Response.Write(“This is text from the classic ASP application” + “<br/>”) %>

When you access this page (in the sample application this is default.asp), the classic ASP ISAPI extension (ASP.dll) will eventually get the chance to parse and run the page, resulting in a string being output to the browser. If you happen to run into a 404 error trying this on IIS6, remember that on IIS6 all known dynamic content extensions are disabled by default, including classic ASP. If you need to enable classic ASP, use the IIS MMC, as shown in Figure 6-5, to enable it again.

It’s time to get a little frisky and see if ASP.NET can output some text in addition to the text coming from the classic ASP application. Try adding the following code to global.asax:

void Application_BeginRequest(Object sender, EventArgs e)

{

HttpContext.Current.Response.Write(“This came from the ASP.NET global.asax event hander”);

}

264

Integrating ASP.NET Security with Classic ASP

Figure 6-5

When you run default.asp, instead of getting back two pieces of text (one from ASP.NET and one from classic ASP), you instead get an error saying, “This type of page is not served.” Hmmm — what happened? First everything was working with the wildcard mapping, and now that you add one simple line of code to ASP.NET and everything breaks!

The reason for this behavior is quite simple. When ASP.NET detects that a response has been modified, prior to handing the request back to IIS6 it checks to see if the request was either a POST request, or a request for a classic ASP page. If the request is a POST request or a classic ASP request ASP.NET will throw an exception rather than hand control back to IIS6. ASP.NET considers a response to have been modified if any of the following occur:

265

Chapter 6

One or more HTTP headers in the response have been set or modified (for example setting a cookie).

Text has been written to the response, regardless of whether this text has been buffered or already sent to the client.

Code in the ASP.NET application modified the HttpCachePolicy associated with the response.

A Stream was assigned to the Response.Filter property. This is an advanced operation and is normally used by developers who need to modify the raw contents of the response prior to sending it back to the browser.

The last two restrictions probably aren’t particularly onerous for developers. However, the first two restrictions effectively mean that you need to be careful about what an ASP.NET application is doing when you use it as a wildcard mapping. If you think about it though, these restrictions do make sense; ASP.NET and classic ASP still live in separate worlds and know nothing about the internal processing logic of the other’s ISAPI extension.

Without some major surgery to the guts of IIS, ASP, and ASP.NET, it is basically impossible for two ISAPI extensions to manipulate the data that is sent back in a response. For example, how would you integrate ASP.NET’s fragment caching with the response written from a classic ASP page? Or how would the response buffering behavior in classic ASP (the Enable Buffering check box for ASP) coexist with response buffering in ASP.NET? The simple answer is that both ISAPI extensions have many internal assumptions about a request lifecycle and around ownership of the actual response data. There isn’t any easy way to reconcile these assumptions in ASP.NET 2.0 or IIS6.

As a side note: This type of coordination is in large part what IIS7 is all about. With support for an integrated pipeline in IIS7, different dynamic content processors like ASP.NET and classic ASP will have a more coherent way of interacting with the request and response data. Though whether either ISAPI extension will be reworked sufficiently to allow ASP.NET and classic ASP to output request content remains to be seen.

Now that you understand that ASP.NET cannot touch anything in the response when interacting with classic ASP, what are some of the things you can safely do in ASP.NET? Any ASP.NET APIs that don’t touch the response are safe to use. So, for example, you can call any of the following:

Forms authentication APIs that create tickets as well as encrypting and decrypting string representations of the tickets. However you cannot call methods like SetAuthCookie or

RedirectFromLoginPage.

Application services that don’t directly interact with the Response object are safe to call. You could call most of the Membership, Role Manager and Profile APIs without any problems.

You can freely use the Request object to inspect information; you could look at the forms authentication cookie (if one was sent) or query-string and forms variables.

You can access other application services such as session state or the Cache API.

As a simple example, you can take the sample ASP.NET application used earlier, and instead of touching the Response, log information about the incoming request to a text file:

266

Integrating ASP.NET Security with Classic ASP

void Application_BeginRequest(Object sender, EventArgs e)

{

//HttpContext.Current.Response.Write(“This came from the ASP.NET global.asax event hander”);

StreamWriter sw = File.CreateText(Server.MapPath(“~/App_Data/logfile.txt”)); sw.WriteLine(“A request was made to: “ + Request.Path);

sw.Flush();

sw.Close();

}

If you access default.asp, everything still works, and the ASP.NET applications App_Data directory contains the text log file containing information about the request. So, you can safely carry out complex operations from inside of the ASP.NET application. From a design standpoint, this means you can think of a wild-carded ASP.NET application as something of a bridge to the managed world for a classic ASP application.

At this point, you might be thinking there is a sneaky way to start doing interesting “stuff” inside of ASP.NET and then pass the results off to classic ASP. Obviously, from the previous sample you could hack up an approach whereby ASP.NET writes information to a file in a common location, and classic ASP read from it. But that approach is going to fall apart quickly. How about just stuffing information onto the query string inside of ASP.NET and then picking these values up over in the classic ASP code?

Request.QueryString.Add(“foo”, “It would be nice if this worked.”);

This code is a nice idea, but it isn’t going to work because inside of ASP.NET information such as Request.QueryString and Request.Form are contained in read-only collections. You could write code inside of the classic ASP application that would place values on the query-string, and then when a redirect occurred the ASP.NET application could read these values and do some work, but the problem that is being addressed in this chapter involves authentication and authorization. In these cases, the flow of data is in the other direction; you need ASP.NET to communicate the results of an authentication or authorization decision to the classic ASP application (or at least store the results in a way that protects the classic ASP application).

Of course, the issue with using all of the ASP.NET capabilities is that the results are still “locked up” as it were inside of the ASP.NET application. How do you actually throw any of the data over the wall to the classic ASP application? Prior to ASP.NET 2.0, you would probably pursue options such as:

Write a Web Service that wraps managed code, and then access it using SOAP tools from your classic ASP applications

Wrap the managed code into a COM component thus making the logic available to the classic ASP world as well.

Both of these approaches are still valid in the world of ASP.NET 2.0. However, they also tend to be a bit heavyweight. Writing a Web Service or a COM-callable wrapper to an inventory control API might make sense, sometimes all you want to accomplish is basic authentication and authorization. Even for these two aspects of a website, writing a Web Service and making something like forms authentication globally available as a service can be appealing.

267

Chapter 6

However, considering that forms authentication and URL authorization are already built into ASP.NET, it seems like overkill to wrap these features just to make them useful in classic ASP. And there is also the extra overhead of having to write and maintain the wrappers as well as figure out how to configure them in production. A much easier approach would be to use these types of ASP.NET features from inside an ASP.NET code-base and make the results available as necessary to the classic ASP application.

The Verify That File Exists Setting

You might have noticed the dialog box for creating a wildcard mapping had a check box that was checked on by default. The Verify that File Exists setting tells IIS6 that it should first verify that the requested resource actually exists on the filesystem, prior to passing the request on to ASP.NET. If you use wildcard mappings for only basic ASP.NET processing, this may be an acceptable setting.

However, if you look at the default file associations that are mapped to ASP.NET, you will see quite a few mappings that have this setting turned off. As a result, if you plan to run application running in IIS6 that contains a mixture of ASP.NET and ASP content, you should leave this setting unchecked. The reason is that a number of “resources” that are requested from an ASP.NET site don’t physically exist on the filesystem.

The easiest way to demonstrate this is by dropping a TreeView control onto a form and hooking it up to a sitemap file:

<asp:TreeView ID=”TreeView1” runat=”server” DataSourceID=”SiteMapDataSource1”> </asp:TreeView>

<br />

<asp:SiteMapDataSource ID=”SiteMapDataSource1” runat=”server” />

If you add a web.sitemap file to a project and the ASP.NET application is configured with a wildcard mapping, when the TreeView renders all collapse icons will be missing. Furthermore, the page will load with a JavaScript error because the HTML source for the page contains references like:

<img src=”/Chapter6/wildcardmappings/WebResource.axd?d=I- aujLBtfk80PyahWsZqq5Fvc9CRO5RKez393GBkAZ41&t=632463106712522616” alt=”” />

These types of references point back at webresource.axd, the central content handler in ASP.NET 2.0 for serving up JavaScript and images. If the Verify that File Exists check box is checked, then IIS6 will fail requests like these because it cannot locate any file called webresource.axd on the filesystem.

Because webresource.axd serves the JavaScript used by validator controls, and it is likely that you will need the validator controls for any ASP.NET login page that front-ends a classic ASP site, remember that you must uncheck this setting when setting up a wildcard mapping.

DefaultHttpHandler

All of the previous discussions have lead up to the need for some kind of “glue” that ASP.NET can use to pass data to classic ASP. The solution to this need is the DefaultHttpHandler class. In the previous examples, it was the DefaultHttpHandler that was responsible for passing the request back to IIS6 whenever an ASP page was requested. Also, it was the DefaultHttpHandler that performed the various checks to ensure that the response had not been modified prior to either processing a POST request or passing control to classic ASP.

268

Integrating ASP.NET Security with Classic ASP

The DefaultHttpHandler runs during the handler execution phase of the ASP.NET HTTP pipeline. In other words, DefaultHttpHandler runs at the same point in time as the .aspx page handler; although instead of running an .aspx page, the DefaultHttpHandler deals with handing control to IIS6. This means that the earlier events in the HTTP pipeline are available, and any of the logic associated with those events will run (For example, the FormsAuthenticationModule will run during

AuthenticateRequest, and so on)

The DefaultHttpHandler is configured in the root web.config file as shown here:

<add path=”*” verb=”GET,HEAD,POST” type=”System.Web.DefaultHttpHandler” validate=”True” />

Because this handler mapping is the second to last mapping, it means that any GET, HEAD, or POST request made to an ASP.NET application for a file type other than ones that are explicitly recognized by ASP.NET, will be routed to the DefaultHttpHandler. Prior to the configuration for DefaultHttpHandler, the default root web.config contains a number of obvious mappings (for example, .aspx requests are mapped to the PageHandlerFactor) and some other not so obvious mappings (for example, SQL Server

.mdf and .ldf files are mapped to the ForbiddenHandler).

If a request is made for an unrecognized file type, but the HTTP verb for the request is not GET, HEAD, or POST, then the request will bypass the DefaultHttpHandler and fall through to the final handler mapping, which points at the HttpMethodNotAllowedHandler. Chapter 2 showed number of examples of using these handler mappings as a way to explicitly block and prevent browser-based access to various file types.

Internally, the DefaultHttpHandler has two code paths: one that eventually hands control back to IIS, and a separate path that handles the case where the response has already been modified in some manner. On one hand, when an ASP.NET application modifies the response, if the DefaultHttpHandler determines that the request is really for a static file, then the DefaultHttpHandler passes the request to another internal handler called the StaticFileHandler. On the other hand, if the DefaultHttpHandler determines that the conditions for passing control back to IIS6 have not been violated, the handler passes control back to IIS6 using the HSE_REQ_EXEC_UNICODE_URL server support function in the ISAPI API.

Normally this means that requests for any kind of non-ASP.NET resource will be automatically routed to IIS6, at which point IIS6 will either serve the file itself (in the case of static files), or pass the request on to the appropriate ISAPI extension (in the case of ASP pages). There is a boundary scenario with static files in that you can programmatically configure an HttpCachePolicy for the Response when a request is made for a static file (remember this is one of the conditions the DefaultHttpHandler checks for). Doing so allows you to use some aspects of ASP.NET output caching to explicitly configure the way you want to cache static file content. Because the cache policy is modified, the DefaultHttpHandler will never pass the request back out to IIS6; there isn’t any logic in IIS6 that would know what to do with an ASP.NET HttpCachePolicy. So, instead the internal StaticFileHandler is used to serve the static content, taking into account the output cache settings set on the Response.Cache property. Because the StaticFileHandler defaults a number of output cache settings, programmatically modifying the response’s cache policy in such a way that it plays well with the StaticFileHandler is tricky — it is also an extensibility scenario that really hasn’t been tested extensively.

269

Chapter 6

Using the DefaultHttpHandler

The DefaultHttpHandler is a public class with a number of virtual methods that you can override. As a first step towards integrating ASP.NET authentication and authorization with classic ASP, you can create a custom HttpHandler that derives from DefaultHttpHandler:

public class CustomHandler : DefaultHttpHandler

{

public CustomHandler() {}

public override string OverrideExecuteUrlPath()

{

//gets called just before control is handed back to IIS6 return null;

}

public override void EndProcessRequest(IAsyncResult result)

{

//gets called when the original ISAPI extension is done processing //This step is useful for post-processing base.EndProcessRequest(result);

}

}

This code represents the basic skeleton of a custom HttpHandler. It overrides the two core methods available on DefaultHttpHandler: OverrideExecuteUrlPath and EndProcessRequest. You want to override the method OverrideExecuteUrlPath rather than the virtual BeginProcessRequest method for the following reasons:

Although you could override BeginProcessRequest, (it is virtual) this method contains the internal logic used by DefaultHttpHandler to determine whether the request can be forwarded to IIS6, or whether the request needs to be passed to the static file handler (or failed in the case of a classic ASP request). The logic for making this determination is internal and, thus, is not accessible to developers.

The OverrideExecuteUrlPath and the OnExecuteUrlPreconditionFailure virtual methods are intended as the two integration points for custom handlers when the request is being processed. Although this chapter deals only with OverrideExecuteUrlPath, you also have the option to override OnExecuteUrlPreconditionFailure. This second method is called when the DefaultHttpHandler determines that the current request cannot be passed to IIS6; if you know that you don’t want the static file handler attempting to process your requests, then you can override OnExecuteUrlPreconditionFailure and throw some other kind of error instead.

The DefaultHttpHandler will have already populated the protected Context property for you before calling into OverrideExecuteUrlPath. Without access to a valid HttpContext, there wouldn’t be much point in writing a custom handler in the first place.

Unlike BeginProcessRequest, you can override EndProcessRequest if needed. For purposes of this chapter nothing needs to be cleaned up or postprocessed in an override of EndProcessRequest. However, if you were attempting to integrate session state between ASP.NET and classic ASP, overriding EndProcessRequest would be the correct place to write session data modified in classic ASP back into the ASP.NET session state store. (Of course, the whole issue with integrating ASP.NET and classic ASP session state would warrant at least part of another book.)

270

Integrating ASP.NET Security with Classic ASP

The current sample code doesn’t actually do anything inside of the overrides. EndProcessRequest simply delegates control to the base class. OverrideExecuteUrlPath returns a null value, which in the case of an ASP.NET application applying authentication and authorization logic to a classic ASP application is the correct thing to do. If you return a null value, the currently requested path is the one that IIS6 will continue executing when it regains control of the request.

The secondary idea behind OverrideExecuteUrlPath, and the reason that it returns a string value, is that developers can choose to modify the actual path that is returned back to IIS6. As a quick side note, if you were to change the logic inside of OverrideExecuteUrlPath to look as follows:

public override string OverrideExecuteUrlPath()

{

//gets called just before control is handed back to IIS6 return “/Chapter6/wildcardmappings/default2.asp”;

}

. . . when you ran the sample application and request default.asp, the actual classic ASP page that would run would be default2.asp. This is a pretty powerful extensibility point but again not something that you need for front-ending a classic ASP application. Some Microsoft development teams, such as Sharepoint, use this ability to modify the path prior to passing control to the Sharepoint ISAPI extension.

Having had written a custom HttpHandler, you still need to register the handler with ASP.NET so that it recognizes it.

<httpHandlers>

<add path=”*.asp” verb=”GET,HEAD,POST” type=”CustomHandler” validate=”true” /> </httpHandlers>

You register HTTP handlers inside of the <httpHandlers /> configuration element. In this case, because the custom handler is intended to work with only classic ASP pages, the path attribute is set to *.asp. You want the custom handler to work with any of the likely HTTP verbs, so GET, HEAD, and POST are all specified. The type registration is simply a .NET Framework type string. In the sample application the CustomHandler class is located inside the App_Code directory, so only the classname is needed. Because I didn’t add an explicit namespace definition in the file located in App_Code, the class ends up in the default namespace and hence does not include a namespace in the type definition. Chances are that in a real production scenario you would implement the custom handler in a standalone assembly, in which case the type attribute requires the namespace qualified class name and at least an assembly reference — something like MyNamespace.CustomHandler, TheHandlerAssembly.

Although the default HTTP handler definitions in the root web.config include a mapping of *.* to the DefaultHttpHandler, the previous registration is still sufficient. When ASP.NET processes the set of defined <httpHandlers />, it will see the handlers defined in the application’s web.config file after the handlers defined in the root web.config file. Because the last matching handler definition takes precedence, the mapping to *.asp inside of the application’s web.config will always win out over the more generic mapping defined in the root web.config file.

To see if everything is working at this point, you can set some breakpoints inside of CustomHandler, and then run the application requesting the default.asp page. The breakpoint in OverrideExecuteUrlPath is hit first (as expected — this also shows that the DefaultHttpHandler is ready to forward the request to IIS6). Later the breakpoint in EndProcessRequest is reached as well. And finally the output from the classic ASP page appears in your browser. So at this point, you have a functioning custom handler and both ASP.NET and classic ASP are working properly.

271