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

Asp Net 2.0 Security Membership And Role Management

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

Chapter 9

The name parameter contains the “name” attribute from the provider’s <add /> configuration element, while the config parameter contains all of the other attributes that the configuration runtime found on the <add /> provider element. The provider first makes a sanity check to ensure that it was passed a valid collection of configuration attributes. When a provider is initialized via a static feature provider that in turn uses a configuration class, this sanity check is redundant. However, as mentioned earlier, there isn’t anything that prevents a developer from attempting to new() up a provider and manually initialize it — hence the sanity check.

If a “description” attribute was not supplied in the provider’s <add /> element, or if it was the empty string, then the provider supplies a default description instead. Although the sample doesn’t show it here, this is the point at which the ASP.NET 2.0 providers will fallback and return a localized description for a provider if you did not supply a “description” in configuration. With the “name” and “description” attributes squared away, the provider calls the Initialize implementation on ProviderBase. ProviderBase will automatically hook up these two attributes to the Name and Description properties defined on ProviderBase.

After the base class performs its initialization tasks, the next pieces of code transfer the “color” and “food” attributes from configuration and hook them up to the provider’s Color and Food properties. Notice that the provider treats these attributes as optional and automatically supplies default values if they were not specified in configuration. Because the configuration class for providers treats all attributes other than “name” and “type” as optional, you need to implement code in your custom providers to either enforce additional required attributes or supply reasonable defaults, as shown in the sample provider. Also notice how after each configuration attribute is used, the attribute is removed from the configuration collection with a call to the Remove method.

The next block of logic deals with handling a connection string attribute. The sample feature obviously doesn’t use any type of connection string, but I included the code for handling connection strings because it is pretty likely that many of you writing custom providers will need to deal with connection strings at some point. The sample provider requires a “connectionStringName” attribute on each provider <add /> element. If it doesn’t find the attribute in the attribute collection passed to Initialize, the provider throws an exception.

Assuming that the attribute was defined, the provider goes through the following series of steps to get the actual connection string:

1.The provider gets a reference to the strongly typed configuration class for the <connection Strings /> configuration section. Remember that this is a new section in the 2.0 Framework and is intended to be the place for storing database connection strings (as opposed to

<appSettings />).

2.The provider looks for the connection string defined by “connectionStringName” in the <connectionStrings /> section. If there is no such connection string with that name, the provider throws an exception.

3.The provider gets the value of the specified connection string and performs a basic verification to ensure it was not set to the empty string. If the connection string’s value was set to the empty string, the provider throws an exception.

4.The provider stores the connection string internally and then removes the “connectionStringName” attribute from the configuration attribute collection.

362

The Provider Model

By this point, the provider and ProviderBase have processed all of the configuration attributes that are known to the two classes. As a final verification, the provider checks to see if there are any remaining attributes in the configuration attribute collection. If there are remaining attributes, the provider throws an exception because it doesn’t know what to do with them. This is an important design point because all of the ASP.NET 2.0 providers perform similar processing with their configuration attributes. For example, if you were to supply additional attributes when configuring a SqlMembershipProvider, the provider would fail with a similar exception.

One subtle point with the way the Initialize method is coded is that it is possible for the provider to fail initialization and end up in a sort of “zombie” state; the provider exists in memory, but it hasn’t completed enough of its initialization to be of any use. Theoretically, if you could get a reference to a zombie provider, you could call properties and methods on it, and depending on when the provider initialization failed, you would get different results. It turns out that the ASP.NET 2.0 providers also have the same small loophole. The ASP.NET providers don’t have extra protections that throw exceptions from public properties or methods because these protections already exist in the static feature classes. Assuming that you aren’t trying to create and initialize providers manually, the static feature classes will fail initialization when one of the configured providers throws an exception from an Initialize call. This, in turn, means that if you attempt to get a reference to a configured provider via a call to either the Provider or Providers properties on the static feature class, you will also get an exception.

This behavior holds true for the sample feature as well. If a provider fails initialization, attempting to call

SampleFeatureMainEntryPoint.Provider (or Providers) will return a TypeInitialization Exception, and you won’t actually be able to get a reference to a “zombie” provider. Of course, you could still attempt to manually create and initialize a provider, but this approach is outside the intended usage boundaries of provider-based features. You can certainly implement additional protections in your providers to cover this case, but because a developer cannot “accidentally” misuse a provider when going through a static feature class, this design loophole was not addressed in the 2.0 Framework.

Now that you have the end-to-end sample feature coded up (finally!), let’s actually try it out in a few scenarios. You can compile all of the previous code into a standalone assembly. Then reference the assembly from a console application that has the following configuration:

<configuration>

<configSections>

<section name=”sampleFeature” type=”SampleFeature.SampleFeatureConfigurationSection, SampleFeature” allowDefinition=”MachineToApplication” />

</configSections>

<sampleFeature > <providers>

<add name=”DefaultSampleFeatureProvider” type=”SampleFeature.SampleFeatureProviderImplementation, SampleFeature” connectionStringName=”SomeConnectionString”

color=”red”

food=”burgers”

description=”this came from config” />

<add name=”SecondSampleFeatureProvider” type=”SampleFeature.SampleFeatureProviderImplementation, SampleFeature”

363

Chapter 9

connectionStringName=”SomeConnectionString”

color=”green” food=”milk-shake” />

<add name=”ThirdSampleFeatureProvider” type=”SampleFeature.SampleFeatureProviderImplementation, SampleFeature” connectionStringName=”SomeConnectionString” />

</providers>

</sampleFeature>

<connectionStrings>

<add name=”SomeConnectionString” connectionString=”the connection string value” />

</connectionStrings>

</configuration>

The test application’s configuration includes the <section /> that tells the configuration system how to parse the <sampleFeature /> configuration element. There are three providers defined for the sample feature. Notice how the “defaultProvider” is not defined on the <sampleFeature /> element while there is a provider <add /> element using the default value for this attribute of “DefaultSampleFeatureProvider.” The second and third provider definitions do not include a “description,” whereas the third provider definition defines the bare minimum number of required attributes (that is, “name,” “type,” and “connectionStringName”). Last, there is a <connection Strings /> section that all of the provider definitions reference.

You can use the feature with the following sample test console application:

using System;

using SampleFeature;

namespace SampleFeatureConsoleTest

{

class Program

{

static void Main(string[] args)

{

try

{

Console.WriteLine( SampleFeatureMainEntryPoint.GetMeAString(“console app”));

}

catch(Exception ex) { }

SampleFeatureProvider sp = SampleFeatureMainEntryPoint.Providers[“SecondSampleFeatureProvider”];

string anotherString = sp.GetMeAString(“Using the second provider.”); Console.WriteLine(anotherString);

SampleFeatureProvider sp2 = SampleFeatureMainEntryPoint.Providers[“ThirdSampleFeatureProvider”];

364

The Provider Model

string anotherString2 = sp2.GetMeAString(

“This provider had no config attributes defined.”); Console.WriteLine(anotherString2);

}

}

}

The sample application works just as you would expect any other provider-based feature to work. With just the provider definition in configuration, it calls the static feature class to output a string. Internally, this results in a call to the default provider. The other two code blocks demonstrate accessing the two nondefault providers and then calling methods directly on them. The sample output is:

This string came from the SampleFeatureProviderImplementation. The provider description is: this came from config

The provider color is: red The provider food is: burgers console app

This string came from the SampleFeatureProviderImplementation.

The provider description is: This would be where you put a localized description for the provider.

The provider color is: green The provider food is: milk-shake Using the second provider.

This string came from the SampleFeatureProviderImplementation.

The provider description is: This would be where you put a localized description for the provider.

The provider color is: The default color for the provider The provider food is: The default food for the provider This provider had no config attributes defined.

You can see how the description varies between the providers, with the second and third providers relying on the default description defined inside of the provider’s Initialize method. The output from the third provider also demonstrates how the provider can fallback to reasonable defaults when option feature-specific attributes are not defined in the provider’s configuration.

If you run the sample console application along with the sample provider code in a debugger, you can play around with intentionally creating bad configurations. Then you can see how the exception behavior inside of the static feature class’s Initialize method causes the second and third attempts to call into the feature to fail (this is why the test app eats all exceptions from the first attempt to use the feature).

Just for grins, you can take the sample feature and drop it into the “/bin” directory of a web application. Take the configuration section shown for the sample console application and drop it into the web

.config for a sample web application. Then create a test page with roughly the same code as shown above for the console application and have it write out the results to a web page. You will get the exact same feature behavior as was demonstrated for the console application.

365

Chapter 9

Summar y

The 2.0 Framework introduces a new design concept with provider-based features. Rather than creating features and services where the internal implementations are “black boxes,” the new provider-based features allow you to author custom implementations of business logic and data access logic. You can then swap these custom implementations into place with a few simple configuration settings.

The core design pattern used by provider-based features is the Strategy pattern. The Strategy pattern is a design approach that allows you to plug in different implementations for the core logic of a feature. In the case of the 2.0 Framework and ASP.NET 2.0, the providers are the implementation of the Strategy design pattern.

A number of support classes exist in System.Configuration, System.Configuration.Providers and System.Web.Configuration to make it easier to write provider-based features yourself. You can use the existing provider base class in conjunction with provider-specific configuration classes to build the basic underpinnings of a provider-based feature.

Overall the sample provider-based feature that was shown had roughly 200 lines for code (and that includes the braces!). Approximately half of the code is boilerplate implementation of things like the provider collection and the configuration class. However, with around only 100 lines of actual initialization code (and again the basics of initialization are the same regardless of feature), you can create a custom provider-based feature that you can use across the spectrum of fat client and web-based applications.

366

Membership

One of the unique aspects of ASP.NET 2.0 is that it introduces a number of powerful new application services that are built using the provider model. Membership is one of the new services and addresses the common need that websites have for creating and managing users and their credentials. Although the Membership feature ships with a great deal of functionality right out of the box, it is also flexible enough for you to customize or extend many of the core aspects of the feature.

This chapter discusses the core classes of the Membership feature: The public static Membership class, the base MembershipProvider class, and the MembershipUser class all include functionality that is common regardless of the kind of providers used with the feature. You will see the various coding assumptions baked into the Membership feature for each of these classes. MembershipProvider is covered in detail so that you get a better idea about what needs to be implemented as well as the general behavior that ASP.NET expects from custom providers.

Last, you gain some insight into miscellaneous design concepts and areas of the Membership feature. The idea of user uniqueness is covered along with guidance about how to create a custom hash algorithm for use by providers. You also see how you can use the Membership feature in applications other than ASP.NET websites.

This chapter will cover the following topics:

The Membership class

The MembershipUser Class

The MembershipProvider base class

The “primary key” for membership

Suypported environments

Using custom Hash algorithms

Chapter 10

The Membership Class

Probably the first exposure many of you had to the Membership feature was through the similarly named Membership class. This class is defined as a public static class, and the style of programming you use with it is meant to parallel other common ASP.NET classes such as Request, Response, and so on. Rather than having to muddle around trying to figure out how to get up and running with the feature, the idea is that after developers know of the Membership class, they can quickly access the functionality of the feature.

As with many provider-based features, the most important task the Membership class provides has already completed before any of your code does anything substantial with the feature. The previous chapter, on the provider model, showed how a static feature class is responsible for initializing a feature, including the instantiation and initialization of all providers associated with the feature. Because the Membership class is static, it performs initialization only once during the lifetime of an application. Furthermore, it instantiates only one instance of each configured MembershipProvider. So, if you plan on writing custom MembershipProviders, you need to follow the guidelines from Chapter 9 to ensure that your custom providers are thread-safe in all public properties and methods.

Although the Membership class is static, for historical reasons (the Membership feature was implemented very early on in the development cycle of ASP.NET 2.0) the class doesn’t take advantage of the Framework’s support for static constructors. Instead, if you were to disassemble the class you would see that is has an internal initialization method that implements locking semantics to ensure that it initializes the feature only once. Furthermore, scattered (or perhaps more accurately — liberally spammed) through all of the properties and methods are internal calls to the initialization method to ensure that the feature has parsed configuration and instantiated providers before attempting to do anything substantial with the feature.

If you look at the public signature of the Membership class, the properties and methods are broken down into three general areas:

Public properties that mirror data loaded from configuration

Public methods that are just facades on top of the underlying default provider

Utility methods that can be used by providers

Before delving into each of these areas though, you need to be familiar with the difference between the feature’s providers, and the feature’s default provider. By now, you have probably seen many examples of the Membership feature’s configuration. The default configuration can always be found up in machine.config (more on why this is the case a little bit later).

Because you can configure multiple providers for the Membership feature, much of the public API on the Membership static class may seem a bit redundant. Furthermore, you might wonder how a method like Membership.CreateUser maps to all the providers you have configured. This is where the concept of the default provider comes in. The <membership /> configuration element has a defaultProvider attribute that defines the specific provider that the static Membership class “talks” to for much of its API.

<membership defaultProvider=”SomeProviderDefinition”> <providers>

<add name=”SomeProviderDefinition” ... />

<add name=”A_Different_Provider_Definition” ... /> </providers>

</membership>

368

Membership

If you have only one provider defined in configuration, using the static Membership class and getting a reference to the single default provider are pretty much the same thing. The only difference between the two approaches is that the static Membership class provides several convenient overloads that map to the method signatures found on a MembershipProvider. For example, several CreateUser overloads on the static Membership class internally map to the single CreateUser method that is defined on

MembershipProvider.

However, if you have multiple provider references in configuration, it is almost guaranteed that the static Membership class will be of limited use to you. In fact, I would go so far as to say that other than using the Membership class for reading global Membership configuration settings, you probably won’t use the Membership class at all in this scenario. By way of example, even the login controls that rely heavily on the Membership feature don’t make much use of the static Membership class. Instead, the login controls get a reference to individual providers via the Membership.Providers property and then invoke various pieces of functionality directly on the providers with a MembershipProvider reference.

Of all of the properties available on the Membership class, only the following ones are global to the feature:

HashAlgorithmType — This is a string property that echoes back the value of the hashAlgorithmType attribute from configuration. It is mainly of use to custom provider implementers that need to know which hash algorithm an application expects from its providers.

Provider — Returns a MembershipProvider reference to the provider defined by the defaultProvider attribute on the <membership /> configuration element. If you have only one provider, you probably won’t use this property.

Providers — Returns a MembershipProviderCollection containing one reference to each provider defined within the <providers /> element contained within a <membership /> element. If your application needs to use multiple providers, you will become very familiar this property.

UserIsOnlineTimeWindow — Defines the number of minutes that should be used to determine whether a user has been considered active on the site.

Several other static public properties are available on the Membership class, but I won’t list them here. These properties are just part of the Membership façade that maps to the same set of properties on the default provider. So, if you access the Membership.PasswordStrenthRegularExpression property for example, you are really retrieving the value of the PasswordStrengthRegularExpression property from the default Membership provider. There is also a public event definition: the ValidatingPassword event. If you register an event handler with this property, in reality you are registering your event handler with the default provider.

Most of the public methods on the Membership class are also facades that just forward their calls internally to the default provider. The purpose of these façade methods is to make the underlying MembershipProvider API a little less intimidating. As such the façade methods “fill in the blanks” for method overloads that have fewer parameters than their counterparts on the MembershipProvider class. On one hand, for example, administrative methods like Membership.FindUsersByName don’t require you to supply more advanced parameters such as page index or page size; you can just call the narrower overloads on the Membership class without having to juggle the extra information. On the other hand, if you take advantage of this functionality with a 100,000 user data store you will quickly regret not using the wider overloads that support paging.

369

Chapter 10

This leads to a bit of a philosophical question: to use or not to use the façade methods on the static Membership class. If you are just writing a small site for yourself and you want to get up and running with a minimum of hassle, all of the façade methods are reasonable. However, if you plan on having more than a few hundred users on your site, and definitely if you are working on production-grade line- of-business or Internet-facing applications, you should look more carefully at the façade methods that you use. At a minimum, I would recommend using the widest overloads possible because they give you full access to all of the parameters from the underlying MembershipProvider.

To be absolutely flexible though, and to ensure your applications are maintainable over the long haul, you should use the Membership.Providers property to get a reference to the desired provider, and then use the resulting MembershipProvider reference to carry out your tasks. This programming style will give you the flexibility in the future to use multiple providers in your application — something that will be somewhat monotonous to retrofit into an application that relied exclusively on the static

Membership class:

//This is OK for simpler applications

MembershipUser mu = Membership.CreateUser(“I_am_new”,”123password@#”);

//This is better to use for larger applications MembershipProvider mp = Membership.Providers[“Provider_Number_2”];

MembershipCreateStatus status; MembershipUser mu;

mu = mp.CreateUser(“username”, “12password@#”, “email”, “passwordquestion”, “passwordanswer”,

true /*isApproved*/, null /*providerUserKey*/, out status);

Obviously, it is a bit more of a hassle to use the provider directly in this case because the CreateUser overload supports quite a few more parameters. But after you code it this way, it is much easier to swap out providers later, potentially even adding logic that chooses a different provider on the fly based on information supplied by the user. It also makes it easier to adjust the code if you choose to turn on or off features like unique email addresses and self-service password resets.

The third set of methods on the Membership class are utility methods. Currently, there is only one: GeneratePassword. If you write custom providers that support self-service password reset with autogenerated passwords this method comes in handy. The method signature is shown here:

public static string GeneratePassword(int length,

int numberOfNonAlphanumericCharacters)

One mode of self-service password reset automatically generates a random password when a user has forgotten his or her password. Because generating a string random password is a pain to get correct, it is actually a handy utility to have around.

The method generates a random string of characters based on the length parameter. Furthermore, it will ensure that at least a number of these characters are considered to be nonalphanumeric characters (for example, Char.IsLetterOrDigit returns false for a certain number of random characters) based on the second parameter. Note that the method may generate a password with more nonalphanumberic characters than specified by the numberOfNonAlphanumericCharacters parameter; you are only guaranteed that the auto-generated password has at least this many nonalphanumeric characters. Last, the method ensures that each randomly generated character won’t trigger a false positive from

370

Membership

ASP.NET’s request validation functionality. It would be frustrating to say the least if the system autogenerated a new password only for the poor website user to always be rejected on the login page because ASP.NET detected a potentially suspicious character in the submitted password when request validation was turned on.

The MembershipUser Class

Regardless of whether you code against the static Membership class or directly against Membership Providers, you will invariable deal with the MembershipUser class. The MembershipUser class is intended to be a lightweight representation of a user (though in retrospect it is just a tad bit too lightweight — hopefully basic information such as first name, last name, and/or friendly name will be tacked on in a future release). The class is not intended to be an exhaustive or comprehensive representation of everything you would ever want to store about a user.

For ASP.NET 2.0, if you need to store more extensive information about a user, the usual approach is to leverage the Profile feature by defining the additional properties you need within the <profile /> configuration section. Alternatively, you can author a custom provider (perhaps deriving from an existing provider type) that works with a derived version of MembershipUser. Using the Profile feature is definitely the simpler of the two approaches. However, writing a custom provider and custom MembershipUser class is appropriate if you don’t want to use the Profile feature in your website.

The main purpose of the MembershipUser class is to contain the basic pieces of information relevant to authenticating a user. Some of the properties are self-explanatory, but I have listed them here with an explanation for each:

Comment — Intended as the one generic property on MembershipUser that you can use to store any information you deem appropriate. No part of the Membership feature makes use of this property, and it is safe to say that future releases of the Membership feature will also leave this property alone. Although you can go overboard and implement entire universes of functionality with this property, it comes in handy when you need to store just a few extra pieces of information and need a convenient place to put them, perhaps those pesky first name and last name properties!

Username — This is the username that your website users type when logging in. It is also one component of the primary key for users in the Membership feature and other related ASP.NET application services.

CreationDate — The date and time when the user was first created in the back-end data store. The property returns its value as a local date time, but the expectation is that providers store it in universal coordinate date time (UTC).

ProviderUserKey — An alternate representation of the primary key for a MembershipUser. Where Username is considered to be part of the primary key for identifying a user across all ASP.NET features, the ProviderUserKey is a data-store specific primary key for the user. This can be useful when retrofitting a custom MembershipProvider onto an existing data store and you want to integrate it with other features you have written that already rely on a data-store- specific primary key. Note that because this property is typed as object, it is up to you to make the correct type casts within your code.

371