La función de membresía ASP.NET reduce la cantidad de código que tiene para escribir para autenticar a los usuarios y almacenar sus credenciales. Para citar MSDN:
"La membresía ASP.NET le brinda una forma incorporada de validar y almacenar credenciales de usuario. Utiliza la membresía ASP.NET con la autenticación de formularios y / o con los controles de inicio de sesión ASP.NET para los usuarios de autenticación".
El proveedor de membresía debe especificarse en el archivo de configuración web.config. Puede usar su propio proveedor personalizado o uno de los proveedores predeterminados que se envía con el marco .NET, como el proveedor SQLMembershipprovider.
Todos los datos relacionados con el usuario se almacenan en un conjunto de tablas utilizadas por el sistema de membresía ASP.NET. En la mayoría de los casos, usará un conjunto de tablas por aplicación web. Sin embargo, también puede reutilizar el mismo conjunto para almacenar las credenciales del usuario de múltiples aplicaciones web. Esto efectivamente le permite crear una aplicación web que actúe como un portal que le permite iniciar sesión en una de estas aplicaciones "virtuales".
Sin embargo, esto no es posible fuera de la caja. Tiene algo de trabajo por delante, antes de poder admitir aplicaciones dinámicas.
Empecemos ...
Comencemos examinando la siguiente entrada web.config:
Listado 1 - Web.Config ASP.NET Configuración de membresía
< membership defaultProvider = " SqlProvider " >
< providers >
< clear />
< add name = " SqlProvider "
type = " System.Web.Security.SqlMembershipProvider "
connectionStringName = " YourConnectionStringName "
applicationName = " MyApplication "
... />
</ providers >
</ membership >Estas configuraciones configuran su aplicación web para usar SQLMembershipprovider para administrar sus credenciales de usuario. Todos los datos relacionados con sus usuarios se almacenan en las tablas de membresía ASP.NET predeterminadas.
Observación : Este artículo no es un manual sobre la membresía de ASP.NET, debe estar familiarizado con él. Use la herramienta de registro de servidor ASP.NET SQL (ASPNET_REGSQL.EXE) para configurar su base de datos.
Como se mencionó anteriormente, puede almacenar las credenciales de usuario de múltiples aplicaciones web en el mismo conjunto de tablas. Cada aplicación web separa sus datos de usuario al dividirlos, en lo que llamo, un contexto de aplicación. Este contexto de aplicación está definido por la propiedad AplicationName del proveedor de membresía. Como puede ver en el Listado 1, el nombre de la aplicación es "MyApplication".
El SQLMembershipprovider utiliza esta configuración para determinar en qué contexto debe operar, todos los datos del usuario vinculados a este nombre de la aplicación serán accesibles por la biblioteca de membresía ASP.NET y los controles de inicio de sesión.
Lamentablemente, esta configuración está estática en la web.config. No puede cambiarlo fácilmente durante el tiempo de ejecución. Si desea admitir aplicaciones dinámicas, necesita una forma de controlar el valor de la propiedad Aplication Name del proveedor. La solución radica en crear su propio proveedor de membresía ASP.NET.
SQLMEMBERSHIPPROVIDER recupera el valor de su propiedad AplicationName de la configuración de configuración contenida en el archivo web.config. Queremos cambiar el valor que devuelve el Getter de esta propiedad.
Al iniciar sesión en el usuario debe aclarar sus intenciones, en otras palabras tiene que especificar a cuál de las aplicaciones virtuales quiere acceder. Para este propósito, necesito que el usuario no solo especifique su nombre de usuario y contraseña, sino también la aplicación virtual a la que desea acceder. Desde el punto de vista del usuario, llamo a esta aplicación virtual, el dominio.
Por ejemplo, suponga que ha establecido una base de datos de SQL Server llamada AspNetMembership que contiene las tablas de membresía ASP.NET. Esta base de datos contiene las siguientes aplicaciones virtuales definidas en la tabla ASPNET_APplications:
Cada aplicación virtual contiene un usuario con el nombre de usuario "CGEERS". Ahora, si quiero iniciar sesión en la aplicación Northwind, tengo que ingresar mi nombre de usuario como "Northwind cgeers". Para la aplicación AdventureWorks, esta sería "AdventureWorks cgeers".
La parte anterior al nombre de usuario es el nombre de la aplicación o en lo que respecta al usuario, este es el dominio que debe ingresar para especificar a qué aplicación virtual desea acceder. El dominio y el nombre de usuario están separados por una barra de retroceso.
Cuando el usuario inicia sesión utilizando el control de inicio de sesión ASP.NET, tenemos que extraer el dominio que ingresó y almacenarlo en una ubicación a la que nuestro proveedor de membresía personalizado puede acceder. Sin embargo, tenemos que tener en cuenta que ASP.NET opera en un entorno multiproceso. Cada solicitud recibida por ASP.NET se maneja mediante un hilo separado. No podemos almacenar el dominio en cualquier lugar, otras que otras solicitudes entrantes podrían sobrescribirlo. Debe estar vinculado a una sola solicitud.
El lugar ideal para almacenar información contextual que está vinculada a una solicitud individual es el contexto HTTP actual. Esta información contextual está encapsulada por la clase HTTPContext.
Inicie Visual Studio y cree una nueva solución en blanco llamada "AspnetDynamicApplices". A continuación, agregue un nuevo proyecto de biblioteca de clases a la solución llamada "CGEers.web.security". Agregue referencias al sistema. Nuestro proveedor personalizado descenderá del valor predeterminado SQLMembershipprovider para que se requieran estas referencias.
Figura 1 - Solución visual de estudio

Cambie el nombre del archivo class1.cs generado automáticamente a DynamicApplicationsSqlMemberShipprovider.cs y agregue el código que se muestra en el Listado 2.
Listado 2 - DynamicApplicationsSqlMemberShipprovider
public class DynamicApplicationsSqlMembershipProvider : SqlMembershipProvider
{
#region Fields
private const string ApplicationNameSetting = "ApplicationName" ;
#endregion
public override string ApplicationName
{
get
{
HttpContext context = HttpContext . Current ;
if ( context == null )
{
throw new InvalidOperationException ( "Http context cannot be null." ) ;
}
string applicationName = String . Empty ;
if ( context . Items . Contains ( ApplicationNameSetting ) )
{
if ( ! String . IsNullOrEmpty ( ( string ) context . Items [ ApplicationNameSetting ] ) )
{
applicationName = ( string ) context . Items [ ApplicationNameSetting ] ;
}
}
return applicationName ;
}
set
{
base . ApplicationName = value ;
}
}
}Como puede ver, nuestro proveedor de membresía personalizado desciende del SQLMembershipprovider predeterminado y anula la propiedad AplicationName. No nos preocupa el setter, solo por el Getter. Cuando el proveedor lee la propiedad AplicationName, obtenemos un manejo del contexto HTTP actual llamando a la propiedad actual estática de la clase HTTPContext. El objeto HTTPContext obtenido tiene una propiedad de colección de clave/valor llamada elementos. Esta colección almacena el nombre de la aplicación en una entrada identificada por la clave "ApplicationName". El nombre de esta clave es algo que debe determinar en el momento de diseño.
Ahora, cada vez que nuestro proveedor de membresía personalizado necesita determinar el nombre de la aplicación, llama a la propiedad AplicationName que recupera el valor del contexto HTTP actual. Esto funciona en el entorno múltiple de ASP.NET ya que cada solicitud está vinculada a su propio contexto HTTP.
Ahora que un proveedor de membresía personalizado que recupera el valor de su propiedad AplicationName del contexto Currect HTTP, todavía tenemos que encontrar una forma de almacenar el nombre de la aplicación en el contexto HTTP.
La primera vez que un usuario especifica la aplicación virtual o el dominio al que quiere acceder es cuando inicia sesión. Como se mencionó anteriormente, el usuario tiene que ingresar su nombre de usuario en el formato <domain><username> . Tenemos que crear nuestro propio control de inicio de sesión que desciende desde el control de inicio de sesión ASP.NET. Tras la autenticación del usuario necesitamos extraer el dominio ingresado y guardarlo en el contexto HTTP actual.
Agregue un nuevo proyecto utilizando la plantilla de biblioteca de clases a su solución llamada "cgeers.web.ui.webcontrols". Agregue las siguientes referencias al proyecto:
A continuación, elimine el archivo class1.cs generado automáticamente y agregue una nueva clase llamada DynamicApplicationsLogin. Dado que el código para esta clase es bastante largo, he elegido dividirlo en varios listados. Después de cada listado se agrega una breve explicación del código.
Listado 3 - DynamicApplicationsLogin Control Propiedades privadas
public class DynamicApplicationsLogin : Login
{
#region Fields
private string _fullUserName ;
#endregion
#region Properties
private string ApplicationName
{
get
{
string [ ] data = base . UserName . Split ( @"" . ToCharArray ( ) , 2 ) ;
string applicationName = ( data . Length == 2 ) ? data [ 0 ] : String . Empty ;
return applicationName ;
}
}
private string BaseUserName
{
get
{
string [ ] data = base . UserName . Split ( @"" . ToCharArray ( ) , 2 ) ;
string userName = ( data . Length == 2 ) ? data [ 1 ] : base . UserName ;
return userName ;
}
}
#endregion
// ...
}Como puede ver, el control DynamicApplicationsLogin desciende del control estándar de inicio de sesión ASP.NET y se agregan un campo llamado _fullusOnname y dos propiedades privadas ApplicationName y BaseUsername.
Como el usuario ingresa al dominio y el nombre de usuario en el mismo control del cuadro de texto, necesitamos dividir estas partes. Como puede suponer que la propiedad AplicationName extrae el dominio y el nombre de BaseUserneN devuelve el nombre de usuario. Por ejemplo, si el usuario ingresa a Northwind cgeers, la propiedad ApplicationName devolvería "Northwind" y la propiedad Baseusername devolvería "CGEERS".
Listado 4 - DynamicApplicationsLogin Control onAuthenticate Método
protected override void OnAuthenticate ( AuthenticateEventArgs e )
{
HttpContext context = HttpContext . Current ;
if ( context == null )
{
throw new InvalidOperationException ( "Http context cannot be null." ) ;
}
MembershipProvider provider = Membership . Provider ;
if ( provider == null )
{
throw new InvalidOperationException ( "MembershipProvider cannot be null." ) ;
}
provider = provider as DynamicApplicationsSqlMembershipProvider ;
if ( provider == null )
{
throw new InvalidOperationException (
"The specified MembershipProvider must be of type DynamicApplicationsSqlMembershipProvider." ) ;
}
// Store the application name in the current Http context's items collections
context . Items [ "ApplicationName" ] = ApplicationName ;
// Validate the user
_fullUserName = UserName ;
UserName = BaseUserName ;
base . OnAuthenticate ( e ) ;
}A continuación, debe anular el método de inauguración del control de inicio de sesión para asegurarse de que el usuario esté validado dentro del contexto de aplicación correcto (aplicación virtual).
Primero, este método recupera una referencia al contexto HTTP actual, luego verifica si el proveedor de membresía cargado es de hecho nuestro DynamicApplicationssqlMembershipprovider personalizado. Luego, el dominio o el nombre de la aplicación ingresado se almacena en la colección de elementos del contexto HTTP actual para que nuestro proveedor de membresía pueda recuperar el valor correcto para su propiedad AplicationName.
Por último, pero no menos importante, el valor de la propiedad del nombre de usuario se establece para contener solo el nombre de usuario asignándole la propiedad BaseuserName. El campo privado _fulluseName almacena temporalmente el valor que ingresó el usuario (dominio username). Verá cómo se usa este campo más adelante.
Después de que el nombre de la aplicación se haya almacenado en el contexto HTTP y la propiedad del nombre de usuario solo contiene el nombre de usuario, se llama la implementación base, esto hace que el nombre de usuario se valida dentro del contexto de aplicación correcto.
Listado 5 -DynamicApplicationsLogin Control Método Onloginerror
protected override void OnLoginError ( EventArgs e )
{
UserName = _fullUserName ;
base . OnLoginError ( e ) ;
}Anule el método OnLoginError y antes de llamar a la implementación base Asigne el campo _fullusOnname a la propiedad del nombre de usuario. Al autenticar el usuario (método onAuthenticate), la propiedad del nombre de usuario solo debe contener el nombre de usuario y no el dominio, pero cuando la autenticación falla, debe asegurarse de que el texto que el usuario ingresó en el control del cuadro de texto que representa el nombre de usuario se restablece al valor que el usuario ingresó inicialmente. Otros que pueden ser confusos para el usuario si el dominio en el que ingresó desaparece.
Listado 6 - Método DynamicApplicationsLogin Control onloggedin
protected override void OnLoggedIn ( EventArgs e )
{
UserName = _fullUserName ;
HttpContext context = HttpContext . Current ;
if ( context == null )
{
throw new InvalidOperationException ( "Http context cannot be null." ) ;
}
string userName = BaseUserName ;
MembershipUser user = Membership . GetUser ( userName ) ;
if ( user != null )
{
string userData = String . Format ( "AN={0};" , ApplicationName ) ;
HttpCookie cookie = FormsAuthenticationHelper . StoreUserDataInAuthenticationCookie (
userName , userData , RememberMeSet
) ;
// Manually add the cookie to the Cookies collection
context . Response . Cookies . Add ( cookie ) ;
}
}Para terminar el control DynamicApplicationsLogin, debe anular el método OnloggedIn. El nombre de la aplicación se almacena en la propiedad UserData de un FormSauthenticationTicket. Este boleto de autenticación es utilizado por la autenticación de formularios para identificar a un usuario autenticado. Aquí lo almacenamos en una cookie y lo agregamos a la respuesta del contexto HTTP actual. Más adelante, verá por qué se requiere esto.
Usted eche un vistazo al Listado 6, verá que la cookie que contiene el boleto de autenticación de formularios es creada por una clase de ayuda llamada FormSauthenticationHelper. Esta es una clase de ayuda estática que solo contiene un método, a saber, StoreUserDatainauthenticationCookie.
Agregue una nueva clase llamada FormSauthenticationHelper al proyecto CGEers.web.security y agregue el código que se muestra en el Listado 7.
Listado 7 - FormSauthenticationHelper
public static class FormsAuthenticationHelper
{
public static HttpCookie StoreUserDataInAuthenticationCookie (
string userName , string userData , bool persistent )
{
if ( String . IsNullOrEmpty ( userName ) )
{
throw new InvalidOperationException ( "UserName cannot be null or empty." ) ;
}
if ( String . IsNullOrEmpty ( userData ) )
{
throw new InvalidOperationException ( "User data cannot be null or empty." ) ;
}
// Create the cookie that contains the forms authentication ticket
HttpCookie cookie = FormsAuthentication . GetAuthCookie ( userName , persistent ) ;
// Get the FormsAuthenticationTicket out of the encrypted cookie
FormsAuthenticationTicket ticket = FormsAuthentication . Decrypt ( cookie . Value ) ;
// Create a new FormsAuthenticationTicket that includes our custom user data
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket ( ticket . Version ,
ticket . Name ,
ticket . IssueDate ,
ticket . Expiration ,
persistent ,
userData ) ;
// Update the cookie's value to use the encrypted version of our new ticket
cookie . Value = FormsAuthentication . Encrypt ( newTicket ) ;
// Return the cookie
return cookie ;
}
}El método StoreUserDatainauthenticationCookie (...) recupera la cookie para el usuario especificado (parámetro de nombre de usuario) que contiene el ticket de autenticación de formularios. A continuación, la cookie se descifró para acceder al objeto FormSauthenticationTicket. Luego, se crea un nuevo FormSauthenticationTicket en función del ticket anterior y el valor contenido en el parámetro UserData se almacena dentro de este ticket. Como último paso, el nuevo objeto FormSauthenticationTicket se almacena dentro de una cookie cifrada.
Observación : Las tiendas de control DynamicApplicationsLogin envían esta cookie al usuario, por lo que el cliente debe admitir cookies!
Recapitulemos lo que tenemos hasta ahora.
Por lo tanto, tenemos nuestro proveedor de membresía personalizado que puede, por solicitud, determinar el contexto de la aplicación para operar y tenemos un control de inicio de sesión personalizado que identifica este contexto al requerir que los usuarios precedan a su nombre de usuario con un dominio (= nombre de aplicación).
Esto funciona bastante bien si su aplicación web solo consiste en una página de inicio de sesión. Recuerde que el proveedor de membresía personalizado determina el contexto de la aplicación leyendo el nombre de la aplicación almacenado en el contexto HTTP actual. Con cada solicitud que realice, se crea un nuevo contexto HTTP. Al iniciar sesión en el nombre de la aplicación, se almacena explícitamente dentro del contexto HTTP. Sin embargo, para cualquier solicitud posterior, este no es el caso. De alguna manera, debemos asegurarnos de que el contexto HTTP se inicialice correctamente con el nombre de la aplicación con cada solicitud después de que el usuario haya iniciado sesión.
Como se mencionó anteriormente, el control DynamicApplicationLogin envía una cookie encriptada al usuario después de haber iniciado sesión con éxito. Esta cookie contiene el nombre de la aplicación y se envía junto con cada solicitud posterior. Voila, ahora todo lo que necesitamos es una forma de insertar algo de lógica en la tubería de solicitud ASP.NET, para leer el contenido de esta cookie y almacenar el nombre de la aplicación recuperado en el contexto HTTP.
Un módulo HTTP es perfecto para esta situación. Se llama a un módulo HTTP en cada solicitud y se ejecuta antes y después de procesarse una solicitud.
Agregue una nueva clase llamada DynamicApplicationsModule al proyecto CGEERS.WEB.SECURITY. Esta clase necesita implementar la interfaz ihttpmodule. El código para este módulo HTTP se muestra en el Listado 8.
Listado 8 - DynamicAplicationsModule
public class DynamicApplicationsModule : IHttpModule
{
#region Fields
private const string ApplicationNameSetting = "ApplicationName" ;
#endregion
#region IHttpModule Members
public void Dispose ( )
{ }
public void Init ( HttpApplication context )
{
context . AuthenticateRequest += DetermineApplicationName ;
}
#endregion
private static void DetermineApplicationName ( object sender , EventArgs e )
{
// Access the current Http application.
HttpApplication application = sender as HttpApplication ;
if ( application == null )
{
throw new InvalidOperationException ( "Http application cannot be null." ) ;
}
// Get the HttpContext for the current request.
HttpContext context = application . Context ;
if ( context == null )
{
throw new InvalidOperationException ( "Http context cannot be null." ) ;
}
// Read the application name stored in the FormsAuthenticationTicket
string applicationName = String . Empty ;
if ( context . Request . IsAuthenticated )
{
FormsIdentity identity = context . User . Identity as FormsIdentity ;
if ( identity != null )
{
FormsAuthenticationTicket ticket = identity . Ticket ;
if ( ticket != null )
{
applicationName = ticket . GetApplicationName ( ) ;
}
}
}
// Store the application name in the Items collection of the per-request http context.
// Storing it in the session state is not an option as the session is not available at this
// time. It is only available when the Http application triggers the AcquireRequestState event.
context . Items [ ApplicationNameSetting ] = applicationName ;
}
}El método Init (...) inicializa un módulo HTTP y lo prepara para manejar las solicitudes. Aquí el evento AuthentATicateRequest de HTTPApplication está enganchado al controlador de eventos DetetineApplicationName.
El controlador de eventos DetetineApplicationName (...) verifica si está tratando con una solicitud autenticada y, de ser así, recupera el ticket FormSauthentication que el cliente envió.
Este ticket identifica el contexto de la aplicación en el que operar. El nombre de la aplicación se extrae del ticket utilizando el método getApplicationName () de FormSauthenticationTicket. Este es un método de extensión, lo abordaré en la siguiente sección.
Finalmente, después de extraer el nombre de la aplicación del ticket, se almacena dentro del contexto HTTP para que nuestro proveedor de membresía personalizado pueda leerlo.
Agregue una nueva clase llamada FormSauthenticationTickExtExtensions al proyecto CGEers.web.security y agregue el código que se muestra en el Listado 9.
Listado 9 - FormSauthenticationTickExtExtensions
internal static class FormsAuthenticationTicketExtensions
{
public static string GetApplicationName ( this FormsAuthenticationTicket ticket )
{
// Check if the application name (AN=) is stored in the ticket's userdata.
string applicationName = String . Empty ;
List < string > settings = new List < string > ( ticket . UserData . Split ( ';' ) ) ;
foreach ( string s in settings )
{
string setting = s . Trim ( ) ;
if ( setting . StartsWith ( "AN=" ) )
{
int startIndex = setting . IndexOf ( "AN=" ) + 3 ;
applicationName = setting . Substring ( startIndex ) ;
break ;
}
}
return applicationName ;
}
}Como puede ver en el Listado 6, el control DynamicApplicationLogin guarda el nombre de la aplicación en la propiedad de los datos de usuarios de FormSauthenticationTicket al precedirlo con el prefijo "an =". Por lo tanto, este método de extensión debe tener esto en cuenta al recuperar el nombre de la aplicación de un FormSauthenticationTicket.
Observación : No dude en mejorar la forma en que se almacena el nombre de la aplicación dentro del boleto, pero recuerde implementar el mismo sistema para el control DynamicAplesLogin y este método de extensión.
Similar al control DynamicApplicationsLogin He creado controles que descienden desde los controles ASP.NET Password ReCovery y ChangePassword. Si desea que un usuario pueda recuperar su contraseña y/o cambiarla, debe usar estos controles. Se aseguran de que todo se ejecute dentro del contexto de aplicación correcto.
La implementación es muy similar a la del control DynamicApplicationsLogin, por lo que no enumeraré el código de estos controles aquí. Si desea consultar el código fuente de estos controles, descargue el código fuente que acompaña a este artículo. Puede encontrar estos controles en el proyecto cgeers.web.ui.webcontrols.
El código fuente que acompaña a este artículo también contiene un proyecto de aplicación web de demostración que demuestra DynamicApplicationsSQLMemberShipprovider, los nuevos controles de inicio de sesión, nuestro módulo HTTP personalizado ... y así sucesivamente.
Figura 2 - Solución visual de estudio

Para ejecutar esta aplicación de demostración, siga estos pasos:
Después de seguir estos pasos, debe estar listo para ejecutar la aplicación web de demostración. Creó una base de datos que contiene dos aplicaciones y un usuario para cada aplicación.
Cuando inicia la aplicación web, puede iniciar sesión en cada aplicación especificando el nombre de la aplicación (dominio) seguido de una barra inalcadora y el nombre de usuario. Por supuesto, la contraseña del usuario también es necesaria.
Los controles de inicio de sesión que creamos se ocupan del resto y se aseguran de que esté operando dentro del contexto de aplicación correcto. Cualquier llamada posterior con la biblioteca de membresía ASP.NET activará el proveedor de membresía personalizado para devolver el nombre correcto de la aplicación, asegurándose de que estas llamadas funcionen dentro del contexto de aplicación correcto.
Observación : El archivo web.config de la aplicación web de demostración contiene comentarios que especifican todo lo que necesita hacer para configurar correctamente la aplicación. Asegúrese de revisarlo.
Este artículo le mostró cómo puede aprovechar el sistema de membresía ASP.NET para crear una aplicación de portal que permita a los usuarios iniciar sesión en una de las muchas aplicaciones virtuales.
La primera clave para realizar esto es crear un proveedor de membresía personalizado que se asegure de operar dentro del contexto de aplicación correcto recuperando el valor para la propiedad AplicationName desde una ubicación de almacenamiento que está vinculada a una solicitud individual.
Al crear un control de inicio de sesión personalizado que desciende desde el control estándar de inicio de sesión ASP.NET, podemos determinar a qué aplicación el usuario desea acceder.
Al encapsular esta información en una cookie cifrada, podemos identificar automáticamente la aplicación que el usuario desea acceder para cada una de sus solicitudes.