O recurso de associação do ASP.NET reduz o valor do código que você deve escrever para autenticar usuários e armazenar suas credenciais. Para citar o MSDN:
"A associação do ASP.NET oferece uma maneira integrada de validar e armazenar credenciais do usuário. Você usa a associação do ASP.NET com a autenticação de formulários e / ou com os controles de login do ASP.NET para autenticar usuários".
O provedor de associação precisa ser especificado no arquivo de configuração do Web.config. Você pode usar seu próprio provedor personalizado ou um dos provedores padrão que são enviados com a estrutura .NET, como o fornecedor SQLMemberShipprovider.
Todos os dados relacionados ao usuário são armazenados em um conjunto de tabelas usadas pelo sistema de associação ASP.NET. Na maioria dos casos, você usará um conjunto de tabelas por aplicativo da web. No entanto, você também pode reutilizar o mesmo conjunto para armazenar as credenciais do usuário de vários aplicativos da Web. Isso permite efetivamente criar um aplicativo da Web que atue como um portal, permitindo fazer login em um desses aplicativos "virtuais".
No entanto, isso não é possível fora da caixa. Você tem algum trabalho pela frente, antes de poder apoiar esses aplicativos dinâmicos.
Vamos começar ...
Vamos começar examinando a seguinte entrada web.config:
Listagem 1 - Configuração de associação Web.config ASP.NET
< membership defaultProvider = " SqlProvider " >
< providers >
< clear />
< add name = " SqlProvider "
type = " System.Web.Security.SqlMembershipProvider "
connectionStringName = " YourConnectionStringName "
applicationName = " MyApplication "
... />
</ providers >
</ membership >Essas configurações configuram seu aplicativo da web para usar o SQLMemberShipprovider para gerenciar suas credenciais de usuário. Todos os dados relacionados aos seus usuários são armazenados nas tabelas de associação ASP.NET padrão.
Observação : Este artigo não é uma cartilha sobre a associação do ASP.NET, você deve estar familiarizado com ele. Use a ferramenta de registro do ASP.NET SQL Server (ASPNET_REGSQL.EXE) para configurar seu banco de dados.
Conforme mencionado antes, você pode armazenar as credenciais do usuário de vários aplicativos da Web no mesmo conjunto de tabelas. Cada aplicativo da Web separa seus dados do usuário, particionando, no que eu chamo, um contexto de aplicativo. Este contexto de aplicativo é definido pela propriedade ApplicationName do provedor de membros. Como você pode ver na Listagem 1, o nome do aplicativo é "MyApplication".
O SQLMemberShipprovider usa essa configuração para determinar em qual contexto deve operar, todos os dados do usuário vinculados a este nome do aplicativo serão acessíveis pela biblioteca de membros do ASP.NET e controles de login.
Infelizmente, essa configuração é definida estaticamente no web.config. Você não pode alterá -lo facilmente durante o tempo de execução. Se você deseja oferecer suporte a aplicativos dinâmicos, precisará de uma maneira de controlar o valor da propriedade ApplicationName do provedor. A solução está na criação do seu próprio provedor de associação ASP.NET.
O SQLMemberShipprovider recupera o valor da propriedade ApplicationName das configurações de configuração contidas no arquivo web.config. Queremos alterar o valor que o getter desta propriedade retorna.
Ao fazer login no usuário, deve deixar suas intenções claras, em outras palavras, ele deve especificar qual dos aplicativos virtuais ele deseja acessar. Para esse fim, exige que o usuário não apenas especifique seu nome de usuário e senha, mas também o aplicativo virtual que ele deseja acessar. Do ponto de vista do usuário, chamo esse aplicativo virtual, o domínio.
Por exemplo, suponha que você tenha estabelecido um banco de dados do SQL Server chamado AspNetMembership, que contém as tabelas de associação ASP.NET. Este banco de dados contém os seguintes aplicativos virtuais definidos na tabela ASPNET_APPLICATIONS:
Cada aplicativo virtual contém um usuário com o nome de usuário "CGEers". Agora, se eu quiser fazer login no aplicativo Northwind, tenho que entrar no meu nome de usuário como "Northwind cgeers". Para o aplicativo AdventureWorks, isso seria "Aventureworks cgeers".
A peça que precede o nome de usuário é o nome do aplicativo ou no que diz respeito ao usuário, este é o domínio que ele deve entrar para especificar em qual aplicativo virtual ele deseja acessar. O domínio e o nome de usuário são separados por uma barra de barriga.
Quando o usuário faz login usando o controle de login do ASP.NET, precisamos extrair o domínio que ele inseriu e armazená -lo em um local que nosso provedor de associação personalizado pode acessar. No entanto, temos que levar em consideração que o ASP.NET opera em um ambiente multithread. Cada solicitação recebida pelo ASP.NET é tratada por um thread separado. Não podemos armazenar o domínio em qualquer local, outras solicitações recebidas podem substituí -lo. Deve estar vinculado a uma única solicitação.
O local ideal para armazenar informações contextuais vinculadas a uma solicitação individual é o contexto HTTP atual. Esta informação contextual é encapsulada pela classe HTTPContext.
Inicie o Visual Studio e crie uma nova solução em branco chamada "AspnetDynamicApplications". Em seguida, adicione um novo projeto de biblioteca de classes à solução chamada "cgeers.web.security". Adicione referências ao System.Configuration e System.Web Assemblies. Nosso provedor personalizado descerá do SQLMEMBERSHIPROVER padrão padrão para que essas referências sejam necessárias.
Figura 1 - Solução do Visual Studio

Renomeie o arquivo Class1.cs gerado automaticamente para o DynamICApplicationsSqlMembersHipprovider.cs e adicione o código mostrado na Listagem 2.
Listagem 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 você pode ver, nosso provedor de associação personalizado desce do padrão SQLMembersHiprovider e substitui a propriedade ApplicationName. Não estamos preocupados com o levantador, apenas com o getter. Quando o provedor lê a propriedade ApplicationName, recebemos um identificador no contexto HTTP atual chamando a propriedade atual estática da classe HTTPContext. O objeto HTTPContext obtido possui uma propriedade de coleta de chave/value chamada itens. Esta coleção armazena o nome do aplicativo em uma entrada identificada pela chave "ApplicationName". O nome desta chave é algo que você precisa determinar no tempo de design.
Agora, cada vez que nosso provedor de associação personalizado precisa para determinar o nome do aplicativo, ele chama a propriedade ApplicationName, que recupera o valor do contexto HTTP atual. Isso funciona no ambiente multithread da ASP.NET, pois cada solicitação está vinculada ao seu próprio contexto HTTP.
Agora que nós, um provedor de associação personalizado, que recupera o valor da sua propriedade ApplicationName do contexto HTTP do Currect, ainda precisamos descobrir uma maneira de realmente armazenar o nome do aplicativo no contexto HTTP.
A primeira vez que um usuário especifica o aplicativo ou domínio virtual que ele deseja acessar é quando ele efetua login. Como mencionado anteriormente, o usuário deve inserir seu nome de usuário no formato <domain><username> . Temos que criar nosso próprio controle de login que desce do controle de login do ASP.NET. Após a autenticação do usuário, precisamos extrair o domínio inserido e salvá -lo no contexto HTTP atual.
Adicione um novo projeto usando o modelo da biblioteca de classes à sua solução chamada "cgeers.web.ui.webcontrols". Adicione as seguintes referências ao projeto:
Em seguida, exclua o arquivo Class1.cs gerado automaticamente e adicione uma nova classe chamada DynamICApplicationsLogin. Como o código para esta classe é bastante longo, escolhi dividi -lo em várias listagens. Após cada listagem, uma breve explicação do código é adicionada.
Listagem 3 - DynamicApplicationsLogin Control Private Properties
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 você pode ver, o controle DynamicApplicationsLogin desce do controle de login ASP.NET padrão e um campo chamado _fulLusername e duas propriedades privadas ApplicationName e Baseusername são adicionadas.
Como o usuário insere o domínio e o nome de usuário no mesmo controle da caixa de texto, precisamos dividir essas peças. Como você pode imaginar, a propriedade ApplicationName extrai o domínio e o nome da base retorna o nome de usuário. Por exemplo, se o usuário entrar no Northwind cgeers, a propriedade ApplicationName retornaria "Northwind" e a propriedade Baseusername retornaria "CGEers".
Listagem 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 ) ;
}Em seguida, você precisa substituir o método Onauthenticate do controle de login para garantir que o usuário seja validado dentro do contexto correto do aplicativo (aplicativo virtual).
Primeiro, este método recupera uma referência ao contexto HTTP atual, então verifica se o provedor de associação carregado é realmente o nosso dinâmico ApplicationsSqlMemMembersHiprovider personalizado. Em seguida, o nome de domínio ou aplicativo inserido é armazenado na coleção de itens do contexto HTTP atual, para que nosso provedor de membros possa recuperar o valor correto para sua propriedade ApplicationName.
Por último, mas não menos importante, o valor da propriedade de nome de usuário está definido como contém apenas o nome de usuário atribuindo a propriedade Baseusername. O campo privado _fulLusername armazena temporariamente o valor que o usuário inseriu (domínio nome de usuário). Você verá como esse campo é usado mais tarde.
Após o nome do aplicativo ter sido armazenado no contexto HTTP e a propriedade Nome de usuário contém apenas o nome de usuário que a implementação base é chamada, isso faz com que o nome de usuário seja validado dentro do contexto correto do aplicativo.
Listagem de 5 -dynamicApplicationsLogin Control Onloginerror Método
protected override void OnLoginError ( EventArgs e )
{
UserName = _fullUserName ;
base . OnLoginError ( e ) ;
}Substitua o método Onloginerror e antes de chamar a implementação da base Atribuir o campo _fulLuserName para a propriedade Nome de usuário. Ao autenticar o usuário (método onAuthenticate), a propriedade de nome de usuário deve conter apenas o nome de usuário e não o domínio, mas quando a autenticação falha, você precisa garantir que o texto que o usuário inseriu no controle da caixa de texto que representa o nome de usuário seja redefinido para o valor que o usuário inseriu inicialmente. Além disso, pode ser confuso para o usuário se o domínio que ele entrou desaparecer.
Listagem 6 - DynamicApplicationsLogin Control Onloggedin Método
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 finalizar o controle DynamicApplicationsLogin, você precisa substituir o método onloggedin. O nome do aplicativo é armazenado na propriedade UserData de um FormSauthenticationTicket. Este ticket de autenticação é usado por autenticação de formulários para identificar um usuário autenticado. Aqui o armazenamos em um cookie e o adicionamos à resposta atual do contexto HTTP. Mais tarde, você verá por que isso é necessário.
É preciso dar uma olhada na Listagem 6, verá que o cookie que contém o ticket de autenticação de formulários é criado por uma classe auxiliar chamada FormSauthenticationHelper. Esta é uma classe auxiliar estática que contém apenas um método, a saber, o StoreUserDatainaThenticationCookie.
Adicione uma nova classe chamada FormSauthenticationHelper ao projeto CGEERS.Web.Security e adicione o código mostrado na Listagem 7.
Listagem 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 ;
}
}O método StoreUserDataAinauthenticationCookie (...) recupera o cookie para o usuário especificado (parâmetro de nome de usuário) que contém o ticket de autenticação de formulários. Em seguida, o cookie é descriptografado para acessar o objeto FormSauthenticationTicket. Em seguida, um novo FormSauthenticationTicket é criado com base no ticket antigo e o valor contido no parâmetro UserData é armazenado nesse ticket. Como última etapa, o novo objeto FormSauthenticationTicket é armazenado em um cookie criptografado.
Observação : as lojas DynamicApplicationsLogin Control envia este cookie de volta ao usuário, para que o cliente precise suportar cookies!
Vamos recapitular o que temos até agora.
Portanto, temos nosso provedor de associação personalizado capaz, por solicitação, para determinar o contexto do aplicativo para operar e temos um controle de login personalizado que identifica esse contexto, exigindo que os usuários precedem seu nome de usuário com um domínio (= nome do aplicativo).
Isso funciona muito bem se o seu aplicativo da Web consistir apenas em uma página de login. Lembre -se de que o provedor de associação personalizado determina o contexto do aplicativo lendo o nome do aplicativo armazenado no contexto HTTP atual. Com cada solicitação que você faz, um novo contexto HTTP é criado. Ao fazer login no nome do aplicativo, é explicitamente armazenado no contexto HTTP. No entanto, para quaisquer solicitações subsequentes, esse não é o caso. De alguma forma, precisamos garantir que o contexto HTTP seja inicializado corretamente com o nome do aplicativo com cada solicitação após o login do usuário.
Como mencionado anteriormente, o DynamicApplicationAlogin Control envia um cookie criptografado ao usuário depois de fazer login com sucesso. Este cookie contém o nome do aplicativo e é enviado junto com cada solicitação subsequente. Voila, agora tudo o que precisamos é de uma maneira de inserir alguma lógica no pipeline de solicitação do ASP.NET, a fim de ler o conteúdo deste cookie e armazenar o nome do aplicativo recuperado no contexto HTTP.
Um módulo HTTP é perfeito para esta situação. Um módulo HTTP é chamado em todas as solicitações e é executado antes e depois que uma solicitação é processada.
Adicione uma nova classe chamada DynamICApplicationsModule ao projeto CGEERS.Web.Security. Esta classe precisa implementar a interface IHTTPModule. O código para este módulo HTTP é exibido na Listagem 8.
Listagem 8 - DynamicApplicationsModule
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 ;
}
}O método init (...) inicializa um módulo HTTP e o prepara para lidar com solicitações. Aqui, o evento Authenticaterequest da HttPapplication está ligado ao manipulador de eventos DetermineApplicationName.
O manipulador de eventos DetermineApplicationName (...) verifica se está lidando com uma solicitação autenticada e, se assim, recupera o tíquete FormSauthentication que foi enviado pelo cliente.
Este ingresso identifica o contexto do aplicativo no qual operar. O nome do aplicativo é extraído do ticket usando o método GetApplicationName () do FormSauthenticationTicket. Este é um método de extensão, eu o abordarei na seção a seguir.
Finalmente, depois de extrair o nome do aplicativo do ticket, ele é armazenado no contexto HTTP para que nosso provedor de associação personalizado possa lê -lo.
Adicione uma nova classe chamada FormSauthenticationTickETEXTENSions ao CGEers.Web.Security Project e adicione o código mostrado na Listagem 9.
Listagem 9 - FormSauthenticationTickEtextensions
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 você pode ver na Listagem 6, o Controle DynamicApplicationAlogin salva o nome do aplicativo na propriedade UserData do FormSauthenticationTicket, precedendo -o com o prefixo "An =". Assim, esse método de extensão precisa levar isso em consideração ao recuperar o nome do aplicativo de um FormSauthenticationTicket.
Observação : sinta -se à vontade para melhorar a maneira como o nome do aplicativo é armazenado dentro do ticket, mas lembre -se de implementar o mesmo sistema para o DynamicApplicationsLogin Control e esse método de extensão.
Semelhante ao controle DynamicApplicationsLogin, criei controles que descendem dos controles do ASP.NET senha e MudarPassword. Se você deseja que um usuário possa recuperar sua senha e/ou alterá -la, precisará usar esses controles. Eles garantem que tudo seja executado no contexto correto do aplicativo.
A implementação é muito semelhante à do controle DynamicApplicationsLogin, para que não listei o código desses controles aqui. Se você deseja verificar o código -fonte desses controles, faça o download do código -fonte que acompanha este artigo. Você pode encontrar esses controles no projeto CGEERS.Web.ui.WebControls.
O código -fonte que acompanha este artigo também contém um projeto de aplicativo de demonstração que demonstra o DynamicApplicationsSqlMembersHiprovider, os novos controles de login, nosso módulo HTTP personalizado ... e assim por diante.
Figura 2 - Solução do Visual Studio

Para executar este aplicativo de demonstração, siga estas etapas:
Depois de seguir estas etapas, você deve estar pronto para executar o aplicativo da Demo Web. Você criou um banco de dados que contém dois aplicativos e um usuário para cada aplicativo.
Quando você inicia o aplicativo da Web, você pode fazer login em cada aplicativo, especificando o nome do aplicativo (domínio) seguido de uma barra de barragem e o nome de usuário. É claro que a senha do usuário também é necessária.
Os controles de login que criamos cuidam do resto e certifique -se de operar no contexto correto do aplicativo. Quaisquer chamadas subsequentes com a biblioteca de membros do ASP.NET acionarão o provedor de associação personalizado para retornar o nome do aplicativo correto, garantindo que essas chamadas operem no contexto correto do aplicativo.
Observação : o arquivo web.config do aplicativo da Demo Web contém comentários que especificam tudo o que você precisa fazer para configurar corretamente o aplicativo. Não deixe de conferir.
Este artigo mostrou como pode usar o sistema de associação ASP.NET para criar um aplicativo de portal que permita aos usuários fazer login em um dos muitos aplicativos virtuais.
A primeira chave para perceber isso é criar um provedor de associação personalizado que permita que você opere no contexto correto do aplicativo, recuperando o valor da propriedade ApplicationName de um local de armazenamento vinculado a uma solicitação individual.
Ao criar um controle de login personalizado que desce do controle de login ASP.NET padrão, podemos determinar qual aplicativo o usuário deseja acessar.
Ao encapsular essas informações em um cookie criptografado, podemos identificar automaticamente o aplicativo que o usuário deseja acessar para cada uma de suas solicitações.