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

Asp Net 2.0 Security Membership And Role Management

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

Chapter 2

Figure 2-2

There is an .aspx page located in the application root, as well as in each of the two subdirectories. The application uses forms authentication, with three fixed users defined in configuration:

<authentication mode=”Forms” > <forms>

<credentials passwordFormat=”Clear”>

<user name=”Admin” password=”password”/>

<user name=”DirectoryAUser” password=”password”/> <user name=”DirectoryBUser” password=”password”/>

</credentials>

</forms>

</authentication>

The web.config located in the root of the application initially defines authorization rules as:

<authorization>

<allow users=”Admin”/> <deny users=”*” />

</authorization>

62

Security Processing for Each Request

When attempting to browse to any page in the application, you must log in as the Admin user to successfully reach the page. However, let’s add a web.config file into Directory A with the following authorization rule:

<authorization>

<allow users=”DirectoryAUser” /> </authorization>

Now both the Admin user and the DirectoryAUser can access the web page located in DirectoryA. The reason for this is that, as mentioned earlier, AuthorizationSection merges authorization rules from the bottom up. The result of defining rules in a web.config located in a subdirectory as well as in the application’s web.config is the following evaluation order:

1.First rules from DirectoryA are evaluated.

2.If no match is found based on the combination of verbs, users and roles, then the rules from the application’s web.config are evaluated.

3.If no match was found using the application’s web.config, then the root web.config located in the framework CONFIG directory is evaluated. Remember that the default authorization configuration grants access to all users.

With this evaluation order, DirectoryAUser matches the rule defined in the web.config file located in DirectoryA. However, for the Admin user, no rules matched, so instead the rules in the application’s web.config are consulted.

Now add a third web.config file, this time dropping it into DirectoryB. This configuration file defines the following authorization rule:

<authorization>

<allow users=”DirectoryBUser” /> </authorization>

Because the evaluation order for accessing pages in DirectoryB will first reference the web.config file from DirectoryB, the DirectoryBUser has access to files in the directory. If you log in though with DirectoryAUser, you will find that you can still access the files in DirectoryB. The reason is that when there is a rule evaluation miss from the web.config file in DirectoryB, ASP.NET moves up the configuration hierarchy to next available web.config file — in this case, the one located in DirectoryA. Because that web.config grants access to DirectoryAUser, that user can also access all resources in DirectoryB. The same affect of hierarchal configuration evaluation allows the Admin user access to the all resources in DirectoryB because the application’s web.config file grants access to Admin.

You can also get the same effect, and still centralize authorization rules in a single configuration file, by using <location /> configuration elements. Using <location /> tags, the authorization rules for the subdirectories are instead defined in the application’s main web.config:

<system.web>

<authorization>

<allow users=”Admin”/> <deny users=”*” />

</authorization>

63

Chapter 2

</system.web>

<location path=”Directory_A”> <system.web>

<authorization>

<allow users=”DirectoryAUser” /> </authorization>

</system.web>

</location>

<location path=”Directory_A/Directory_B”> <system.web>

<authorization>

<allow users=”DirectoryBUser” /> </authorization>

</system.web>

</location>

You will have the exact the same login behavior as described earlier when using separate web.config files. The configuration system treats each <location /> tag as a logically separate “configuration” file. The end result is that even though the authorization rules are defined in the same physical web.config file, the <location /> tags preserve the hierarchal nature of the configuration definitions.

Developers sometimes want to control configuration in a central configuration file for an entire web server but are unsure of the value to use for the “path” attribute when referencing individual web applications. For example, if you want to centrally define configuration for an application called “Test” located in the Default Web Site in IIS, you can use the following <location /> definition:

<location path=”Default Web Site/Test” />

So far, the sample application has demonstrated the hierarchal merge behavior of different configuration files and different <location /> elements. If the authorization rule for the Admin user is reversed with the deny rule:

<authorization>

<deny users=”*” /> <allow users=”Admin”/>

</authorization>

The Admin user can no longer access any of the pages. The behavior for DirectoryBUser and DirectoryAUser remains the same because the other <location /> elements grant these users access. But when the last set of authorization rules are evaluated, the blanket <deny /> is evaluated first. As a result, any authorization evaluation that reaches this <authorization /> element always results in access being denied.

Note that even though the previous samples relied on authorizing based on the user’s name, the same logic applies when authorizing based on verb or based on a set of one or more roles.

Of course what can’t be shown here (but you will see the behavior if you download and try out the sample) is the behavior when UrlAuthorizationModule denies access to a user. When the module denies access, it sets Response.StatusCode to 401, writes out some custom error text in the response, and

64

Security Processing for Each Request

then short circuits the request by rerouting it to the EndRequest event (basically, the same behavior as the FileAuthorizationModule). However, for those of you that have used URL authorization before, you know that typically you don’t see an access denied error page. Instead, in the case of forms authentication, the browser user is redirected to the login page configured for forms authentication. If an application is using Windows authentication, the 401 is a signal to IIS to attempt to negotiate credentials with the browser based on the application’s security settings in IIS. In a few more pages, you will look at

how the EndRequest event is handled for security related tasks, and this should give you a clearer picture of the redirect and credential negotiation behavior.

How Character Sets Affect URL Authorization

The character set used to populate the IPrincipal on the context’s User property plays an important role when authorizing access with UrlAuthorizationModule. When performing an access check based on the users attribute defined for an authorization rule, UrlAuthorizationModule performs a caseinsensitive string comparison with the value from HttpContext.Current.User.Name. Furthermore, the comparison is made using the casing rules for the invariant culture and ordering rules based on ordinal sort ordering.

Because of this, there may be subtle mismatches in character comparisons due to a different character set being used for the value of a username. For example, the Membership feature in ASP.NET 2.0 stores usernames in a SQL Server database by default. If a website selects a different collation order than the default Latin collation, the character comparison rules that are applied at user creation time will not be the same as the comparison rules UrlAuthorizationModule applies when comparing usernames.

Overall though, there are two simple approaches to avoid any problems caused by using different character sets for user creation and user authorization:

Don’t authorize based on usernames. Instead only authorize based on roles because the likelihood of any organization creating two role names that differ only in characters with culturespecific semantics is extremely low.

Use a character set/collation order in your back-end user store that is a close match with the invariant culture. For SQL Server, the default Latin collation is a pretty close approximation of the invariant culture. If you are authorizing against WindowsIdentity instances, then you won’t encounter a problem because usernames in Active Directory are just plain Unicode strings without culture-specific character handling.

PostAuthorizeRequest through PreRequestHandlerExecute

After the AuthorizeRequest event, developers can hook the PostAuthorizeRequest event if there is custom authorization work that needs to be performed. ASP.NET does not ship with any HttpModules that hook this event though. After PostAuthorizeRequest, there are no other pipeline events intended for authentication or authorization related processing. Although many of the subsequent pipeline events may use the identity of the current user, the pipeline events up through PreRequestHandlerExecute are intended for setting up and initializing other information such as session state data or cached information used by output and fragment caching.

Technically, you could manipulate the operating system thread identity, the current thread principal, or the current context’s User property during any subsequent pipeline event. However, there is an implicit assumption that after PostAuthenticateRequest the security information for the request is stable, and

65

Chapter 2

that after PostAuthorizeRequest no additional authorization is necessary. Because the pipeline events after PostAuthorizeRequest are involved in retrieving data tied to a user identity (state and cached data), it is important that any custom authentication or authorization mechanism honors these assumptions.

Blocking Requests during Handler Execution

After the PreRequestHandlerExecute event, ASP.NET passes the request to an implementation of IHttpHandler. HTTP handlers are responsible for executing the resource requested by the browser. The most frequently used and recognized HTTP handler is the Page handler. However, ASP.NET ships with a number of different handlers depending on the file extension of the requested resource. From a security perspective though, handler execution is another opportunity to block access to specific resources.

ASP.NET 2.0 ships with four internal HTTP handlers; the classes themselves are defined with the “internal” keyword and, thus, are not directly accessible in code. However, you can still make use of these handlers by defining mappings to them in the <httpHandlers /> configuration section. The <httpHandlers /> section defines mappings between IHttpHandler implementations and file extensions as well as HTTP verbs. For example, the Page handler is routed all requests that end in .aspx because of the following handler registration:

<add path=”*.aspx” verb=”*” type=”System.Web.UI.PageHandlerFactory” validate=”True” />

The default handler mappings are in the root web.config file located in the framework’s CONFIG subdirectory.

The four internal HTTP handlers available for blocking access to file types and HTTP verbs are:

System.Web.HttpNotFoundHandler

System.Web.HttpForbiddenHandler

System.Web.HttpNotImplementedHandler

System.Web.HttpMethodNotAllowedHandler

ASP.NET only uses three of the handlers in the default handler mappings (the HttpNotImplementedHandler is not mapped to anything). For example, the following handler mappings exist in the root web.config file (note this is not an exhaustive list, just a subset of what is defined):

<add path=”*.axd” verb=”*” type=”System.Web.HttpNotFoundHandler” validate=”True” /> <add path=”*.mdf” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”True” />

<add path=”*” verb=”*” type=”System.Web.HttpMethodNotAllowedHandler” validate=”True” />

ASP.NET determines which handler should process a given request by evaluating the handler mappings from top to bottom in configuration. The sample mappings shown above have the following affects:

66

Security Processing for Each Request

1.Attempts to access files ending in .axd are prevented.

2.Files ending in .mdf cannot be retrieved from a browser. Both .mdf and .ldf are file extensions for SQL Server data and log files.

3.The last handler mapping shown also happens to be the very last handler registration in the default configuration for ASP.NET. This mapping ensures that if ASP.NET could not find any other handler for a request, then the HttpMethodNotAllowedHandler is used.

In all cases, the four internal handlers supplied by ASP.NET have the same end result; a request for a resource that is mapped to one of these four handlers will fail. The only difference between the four handlers is their general intent. As the handler names imply, each of them returns a different HTTP status code, which in turn results in different error information being sent back to the browser.

System.Web.HttpNotFoundHandler — The handler terminates further processing in the pipeline (except for the EndRequest event) and returns a 404 error stating that the resource could not be found.

System.Web.HttpForbiddenHandler — The handler terminates further processing in the pipeline (except for the EndRequest event) and returns a 403 error stating that the type of the requested resource is not allowed.

System.Web.HttpNotImplementedHandler — The handler terminates further processing in the pipeline (except for the EndRequest event) and returns a 501 error stating that the requested resource is not implemented

System.Web.HttpMethodNotAllowedHandler — The handler terminates further processing in the pipeline (except for the EndRequest event) and returns a 405 error stating that the requested HTTP verb is not allowed.

Because all of these handlers result in specific HTTP status codes, you can also use the <customErrors /> configuration to reroute these errors to friendlier looking pages.

One of the reasons why it is possible to XCOPY an ASP.NET application, including its code and related project files is that ASP.NET explicitly blocks access to source code on the server with handler registrations such as the following:

<add path=”*.cs” verb=”*” type=”System.Web.HttpForbiddenHandler” validate=”True” /> <add path=”*.csproj” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”True” />

<add path=”*.vb” verb=”*” type=”System.Web.HttpForbiddenHandler” validate=”True” /> <add path=”*.vbproj” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”True” />

Using the exact same approach, you can configure handler mappings to provide an additional level of security for your ASP.NET applications.

Blocking Access to non-ASP.NET File Extensions

Your application may have custom data files that need to reside on the file system, but that you do not want to be retrievable from a browser. For example, all of your data files may end with .xml. If you create only the following handler registration:

<add path=”*.xml” verb=”*” type=”System.Web.HttpForbiddenHandler” validate=”True” />

67

Chapter 2

You will find that XML files are still retrievable in the browser. Think back to the previous chapter, where the distinction between static and dynamic files was discussed. For any of the four ASP.NET handlers to successfully block access to specific file types, the file extensions must be registered in IIS so that the request actually makes it over to ASP.NET in the first place.

I specifically chose the .xml file extension because it has a default MIME type mapping in IIS, which means in the absence of any additional configuration on your part, IIS will happily return XML files back to the browser. Remember that without a MIME type mapping, IIS will not serve a static file.

To rectify this problem, you need to register the .xml file in IIS by associating the .xml file extension with the ASP.NET ISAPI extension. Figure 2-3 shows .xml mapped to the ASP.NET 2.0 ISAPI extension for a sample application.

Figure 2-3

Now that IIS is configured to pass all requests for .xml files over to ASP.NET, the handler registration mapping XML files to .Web.HttpForbiddenHandler takes effect, and a 403 error occurs instead.

Because ASP.NET 2.0 also has the concept of protected directories, for scenarios like XML files containing data, a better choice would be to move all data-related XML files into the App_Data directory. Placing files in ASP.NET 2.0 protected directories automatically protects against attempts to retrieve any file types located in these directories.

68

Security Processing for Each Request

Identity during Asynchronous Page Execution

Earlier in the chapter, I discussed issues with flowing security identities through asynchronous pipeline event handlers. The Page handler in ASP.NET 2.0 also supports the concept of asynchronous execution, and as a result developers using this functionality should be aware of the security identities for this case.

Things can be a little confusing with asynchronous pages because the Page class supports two different patterns for carrying out asynchronous tasks. Both approaches, along with the flow of security information, are discussed in the next two sections.

Asynchronous PreRender Processing

A developer can request asynchronous support in a page by including the Async attribute in the page directive:

<%@ Page Language=”C#” Async=”true” %>

To leverage this asynchronous page model, you need to register a begin and an end event handler for your asynchronous task. This approach is exactly the same model as discussed earlier for asynchronous pipeline events. You typically hook up the async begin and end event handlers inside of a page or control event where a long-running task would normally occur. For example, instead of making a call to a high-latency Web Service from inside of a button click event handler, you would instead register your asynchronous event handlers in the click event handler. Furthermore, you can hook up multiple begin and end event handlers, and ASP.NET will call each pair of asynchronous event handlers in sequence.

ASP.NET calls into your async begin event handler after the PreRender phase of the page lifecycle. The idea is that high-latency work can be safely deferred until the PreRender phase because the results of any processing are not needed until the subsequent Render phase of a Page. Inside of your async begin event handler you collect whatever data you need to pass to your asynchronous task (page variables, context data, and so on), and then you invoke the asynchronous task. As with asynchronous pipeline events, the asynchronous task that is called during asynchronous page processing runs on a .NET thread-pool thread. This means it is your responsibility to gather any necessary security information and “throw it over the wall” to the asynchronous task.

After some indeterminate amount of time has passed, the asynchronous task completes and the ASP.NET runtime is signaled via a callback. Just as you saw with asynchronous pipeline events, the async end event for pages executes on a thread-pool thread. The operating system thread identity at this point will not reflect the security settings you have set in IIS and ASP.NET. Note though that if you implement your async begin and end event handlers as part of the page’s code-behind class, you can always get back to the HttpContext associated with the page (that is, this.Context is available). This at least gives you access to the IPrincipal associated with the request from inside of both the async begin and end event handlers.

After the end event handler runs, ASP.NET reschedules the page for execution, at which point ASP.NET reinitializes the operating system thread identity, managed thread identity, and the HttpContext (including its associated IPrincipal) for the current managed thread.

To demonstrate the security identity handling during asynchronous page execution, you can create an application with a single asynchronous page that registers for asynchronous PreRender handling. The page has a single button on it, and the application registers the async begin and event handlers in its click event.

69

Chapter 2

protected void Button1_Click(object sender, EventArgs e)

{

//Hook up the async begin and end events

BeginEventHandler bh = new BeginEventHandler(this.BeginAsyncPageProcessing);

EndEventHandler eh = new EndEventHandler(this.EndAsyncPageProcessing);

AddOnPreRenderCompleteAsync(bh, eh);

}

Notice that the event handler delegates are of the exact same type used with asynchronous pipeline events. The async begin handler is responsible for triggering the asynchronous work and returns the IAsyncResult reference to ASP.NET.

//defined as part of the page class public delegate void AsyncSleepDelegate();

private IAsyncResult BeginAsyncPageProcessing(

object sender, EventArgs e, AsyncCallback cb, object extraData)

{

//Output the security information

//.. code snipped out for brevity ...

//Do the actual asynchronous work

Sleep s = new Sleep(this.Context.Items); AsyncSleepDelegate asd = new AsyncSleepDelegate(s.DoWork); return asd.BeginInvoke(cb, asd);

}

The async end event handler in the sample application just outputs more security identity information. In a real application, you would gather the results of the asynchronous work and probably set the values of various controls on the page or perhaps data-bind the results to one of the data controls.

private void EndAsyncPageProcessing(IAsyncResult ar)

{

//Normally you would harvest the results of async processing here AsyncSleepDelegate asd = (AsyncSleepDelegate)ar.AsyncState; asd.EndInvoke(ar);

//Output security information

//.. code snipped out for brevity ...

}

As with the asynchronous pipeline event sample, the asynchronous page uses a simple class that sleeps for one second to simulate a long-running task. A reference to the current HttpContext is passed in the constructor so that the class can log the operating system thread identity.

public class Sleep

{

private IDictionary state;

public Sleep(IDictionary appState)

{

70

Security Processing for Each Request

state = appState;

}

public void DoWork()

{

state[“AsyncWorkerClass_OperatingSystemThreadIdentity”] = WindowsIdentity.GetCurrent().Name;

Thread.Sleep(1000);

}

}

I ran the sample application with the following IIS and ASP.NET configuration settings:

1.

2.

3.

The application ran locally on the web server.

Authenticated access was required in IIS.

An explicit application impersonation identity was used for ASP.NET.

The results of running the application with this configuration are shown here:

The OS thread identity during the beginning of page async processing is: CORSAIR\appimpersonation

The OS thread identity in the async worker class is: NT AUTHORITY\NETWORK SERVICE The OS thread identity during the end of page async processing is: NT AUTHORITY\NETWORK SERVICE

The OS thread identity in Render is: CORSAIR\appimpersonation

You can see that the background work and the end event run with the default credentials of the process, despite the fact that the ASP.NET application is configured with application impersonation. Once the page starts running again in the Render event though, ASP.NET has reinitialized all of the security information, and the application impersonation identity is once again used for the operating system thread identity. The exact same approaches for flowing credentials discussed earlier in the section “Thread Identity and Asynchronous Pipeline Events” also apply to the asynchronous PreRender processing.

Asynchronous Page Using PageAsyncTask

An alternative approach to attributing a page as being is the concept of asynchronous page tasks. This second approach has many similarities to the previous discussion. As a developer, you still need to delegate your high-latency work as a piece of asynchronous processing. Additionally, you hook into the PageAsyncTask-based processing with a pair of begin and end event handlers.

However, there are some important differences in the PageAsyncTask approach. You can create one or more asynchronous units of work, wrap each piece of work with individual PageAsyncTask instances and then hand all of the work off as a single “package” to the page. With the PreRender-based approach, handling multiple asynchronous tasks is a little more awkward because you either have to coalesce all of the work yourself inside of a custom class, or you have to carefully hook up a chain of begin and end event handlers.

Also, when you are wrapping your asynchronous work, you can pass a timeout handler to the PageAsyncTask that will execute if your asynchronous work takes too long. The actual timeout that is honored for each piece of asynchronous work defaults to 45 seconds, though this can be changed by

71