Share via


Como Desenvolver um Módulo Usando .NET

por Mike Volodarsky

Introdução

O IIS 7.0 e superior permite estender o servidor por módulos que são desenvolvidos de duas maneiras:

  • Usando código gerenciado e as APIs de extensibilidade do servidor ASP.NET
  • Usando código nativo e as APIs de extensibilidade do servidor nativo do IIS

No passado, os módulos ASP.NET eram limitados em funcionalidade, pois o pipeline de processamento de solicitações ASP.NET era separado do pipeline de solicitação do servidor principal.

No IIS, os módulos gerenciados se tornam virtualmente tão poderosos quanto os módulos nativos com a arquitetura de Pipeline Integrado. Mais importante, os serviços que os módulos gerenciados fornecem agora podem ser aplicados a todas as solicitações ao servidor, não apenas às solicitações para conteúdo ASP.NET, como páginas ASPX. Os módulos gerenciados são configurados e gerenciados de maneira consistente com módulos nativos e podem ser executados nos mesmos estágios de processamento e pedidos que os módulos nativos. Finalmente, os módulos gerenciados podem executar um conjunto mais amplo de operações para manipular o processamento de solicitações por meio de várias APIs do ASP.NET adicionadas e aprimoradas.

Este artigo ilustra a extensão do servidor com um módulo gerenciado para adicionar a capacidade de executar a autenticação básica em um repositório de credenciais arbitrário, como a infraestrutura de credenciais baseada em provedor no sistema de associação ASP.NET 2.0.

Isso permite substituir o suporte interno de autenticação básica no IIS, que está vinculado ao armazenamento de credenciais do Windows, por um que dê suporte a repositórios de credenciais arbitrários ou a qualquer um dos provedores de associação existentes fornecidos com o ASP.NET 2.0, como SQL Server, SQL Express ou Active Directory.

Este artigo examina as seguintes tarefas:

  • Desenvolver um módulo gerenciado usando APIs do ASP.NET
  • Implantar um módulo gerenciado no servidor

Para saber mais sobre os conceitos básicos do desenvolvimento de módulos e manipuladores do IIS, confira Desenvolver módulos e manipuladores do IIS7 com o .NET Framework.

Você também pode encontrar muitos recursos e dicas sobre como escrever módulos do IIS no blog, http://www.mvolo.com/, bem como baixar módulos IIS existentes para seus aplicativos. Para obter alguns exemplos, confira Redirecionamento de solicitações para seu aplicativo com o módulo HttpRedirection, Listas de diretórios interessantes para seu site do IIS com DirectoryListingModule e Exibindo ícones de arquivo bonitos em seus aplicativos ASP.NET com o IconHandler.

Observação

O código fornecido neste artigo é escrito em C#.

Pré-requisitos

Para seguir as etapas neste documento, você deve ter os seguintes recursos do IIS instalados:

ASP.NET

Instale o ASP.NET através do Painel de Controle do Windows Vista. Selecione “Programas” – “Ativar ou desativar recursos do Windows”. Em seguida, abra “Serviços de Informações da Internet” – “Serviços da World Wide Web” – “Recursos de desenvolvimento de aplicativos” e marque “ASP.NET”.

Se você tiver um build do Windows Server® 2008, abra “Gerenciador do Servidor” – “Funções” e selecione “Servidor Web (IIS)”. Clique em “Adicionar serviços de função”. Em “Desenvolvimento de Aplicativos”, marque “ASP.NET”.

Informações gerais sobre autenticação básica

A autenticação básica é um esquema de autenticação definido no protocolo HTTP.1 (RFC 2617). Ele usa um mecanismo padrão baseado em desafio que funciona da seguinte forma em geral:

  • O navegador faz solicitação para uma URL sem credenciais
  • Se o servidor exigir autenticação para essa URL, ele responderá com uma mensagem 401 Acesso Negado e incluirá um cabeçalho que indica que o esquema de autenticação básica tem suporte
  • O navegador recebe a resposta e, se configurado, solicitará ao usuário um nome de usuário/senha que será incluído em texto sem formatação dentro de um cabeçalho de solicitação para a próxima solicitação à URL
  • O servidor recebe o nome de usuário/senha dentro de um cabeçalho e os usa para autenticação

Observação

Embora uma discussão detalhada desse protocolo de autenticação esteja fora do escopo deste artigo, vale a pena mencionar que o esquema de autenticação básico requer que o SSL seja seguro, pois envia o nome de usuário/senha em texto sem formatação.

O IIS inclui suporte para autenticação básica em relação às contas do Windows no armazenamento de contas local ou no Active Directory para contas de domínio. Queremos permitir que nosso usuário se autentique usando a autenticação básica, mas valide as credenciais usando o serviço de associação do ASP.NET 2.0. Isso dá a liberdade de armazenar informações do usuário em uma variedade de provedores de associação existentes, como o SQL Server, sem estar vinculado a contas do Windows.

Tarefa 1: Desenvolver um módulo usando o .NET

Nesta tarefa, examinamos o desenvolvimento de um módulo de autenticação que dê suporte ao esquema de autenticação básica HTTP.1. Este módulo foi desenvolvido utilizando o padrão de módulo ASP.NET standard, disponível desde ASP.NET v1.0. Esse mesmo padrão é usado para criar módulos ASP.NET que estendem o servidor IIS. Na verdade, os módulos ASP.NET existentes escritos para versões anteriores do IIS podem ser usados no IIS e aproveitar a melhor integração ASP.NET para fornecer mais potência aos aplicativos Web que os utilizam.

Observação

O código completo do módulo é fornecido no Apêndice A.

Um módulo gerenciado é uma classe .NET que implementa a interface System.Web.IHttpModule. A função principal dessa classe é registrar um ou mais eventos que ocorrem no pipeline de processamento de solicitações do IIS e, em seguida, executar algum trabalho útil quando o IIS invoca os manipuladores de eventos do módulo para esses eventos.

Vamos criar um novo arquivo de origem chamado “BasicAuthenticationModule.cs” e criar a classe do módulo (o código-fonte completo é fornecido no Apêndice A):

public class BasicAuthenticationModule : System.Web.IHttpModule
{
    void Init(HttpApplication context)
    {
    }
    void Dispose()
    {
    }
}

A função principal do método Init é conectar os métodos de manipulador de eventos do módulo aos eventos de pipeline de solicitação apropriados. A classe do módulo fornece os métodos de identificador de eventos, e eles implementam a funcionalidade desejada fornecida pelo módulo. Isso será discutido mais detalhadamente.

O método Dispose é usado para limpar qualquer estado do módulo quando a instância do módulo é descartada. Normalmente, ele não é implementado, a menos que o módulo use recursos específicos que precisam ser liberados.

Init()

Depois de criar a classe, a próxima etapa é implementar o método Init. O único requisito é registrar o módulo para um ou mais eventos de pipeline de solicitação. Conecte os métodos de módulo, que seguem a assinatura de representante System.EventHandler, aos eventos de pipeline desejados expostos na instância System.Web.HttpApplication fornecida:

public void Init(HttpApplication context)            
{
   //          
   // Subscribe to the authenticate event to perform the 
   // authentication. 
   // 
   context.AuthenticateRequest += new        
              EventHandler(this.AuthenticateUser);

   // 
   // Subscribe to the EndRequest event to issue the 
   // challenge if necessary. 
   // 
   context.EndRequest += new 
              EventHandler(this.IssueAuthenticationChallenge);
}

O método AuthenticateUser é chamado em cada solicitação durante o evento AuthenticateRequest. Nós o utilizamos para autenticar o usuário com base nas informações de credenciais presentes na solicitação.

O método IssueAuthenticationChallenge é chamado em cada solicitação durante o evento EndRequest. Ele é responsável por emitir um desafio de autenticação básica de volta ao cliente sempre que o módulo de autorização rejeitar uma solicitação e a autenticação for necessária.

AuthenticateUser()

Implemente o método AuthenticateUser. O método faz o seguinte:

  • Extrai as credenciais básicas, se presentes, dos cabeçalhos de solicitação de entrada. Para ver a implementação dessa etapa, confira o método do programa utilitário ExtractBasicAuthenticationCredentials.
  • Tenta validar as credenciais fornecidas por meio de Associação (usando o provedor de associação padrão configurado). Para ver a implementação dessa etapa, confira o método do programa utilitário ValidateCredentials.
  • Cria uma entidade de usuário identificando o usuário, se a autenticação for bem-sucedida, e a associa à solicitação.

Ao final desse processamento, se o módulo conseguir obter e validar com êxito as credenciais do usuário, ele produzirá uma entidade de usuário autenticada que outros módulos e o código do aplicativo usarão posteriormente nas decisões de controle de acesso. Por exemplo, o módulo de autorização de URL examina o usuário no próximo evento de pipeline para impor as regras de autorização configuradas pelo aplicativo.

IssueAuthenticationChallenge()

Implemente o método IssueAuthenticationChallenge. O método faz o seguinte:

  • Verifica o código de status da resposta para determinar se essa solicitação foi rejeitada.
  • Em caso afirmativo, emita um cabeçalho de desafio de autenticação básica para a resposta para acionar o cliente para autenticação.

Métodos do programa utilitário

Implemente os métodos do programa utilitário que o módulo usa, incluindo:

  • ExtractBasicAuthenticationCredentials. Esse método extrai as credenciais de autenticação básicas do cabeçalho da solicitação Autorizar, conforme especificado no esquema de autenticação básica.
  • ValidateCredentials. Esse método tenta validar as credenciais do usuário usando Associação. A API de associação abstrai o armazenamento de credenciais subjacente e permite que as implementações de repositório de credenciais sejam configuradas adicionando/removendo provedores de associação por meio da configuração.

Observação

Neste exemplo, a validação de associação é comentada e, em vez disso, o módulo simplesmente verifica se o nome de usuário e a senha são iguais à cadeia de caracteres “test”. Isso é feito para maior clareza e não se destina a implantações de produção. Você é convidado a habilitar a validação de credencial baseada em associação simplesmente removendo o comentário do código de associação dentro de ValidateCredentials e configurando um provedor de associação para seu aplicativo. Confira o Apêndice C para obter mais informações.

Tarefa 2: Implantar o módulo no aplicativo

Depois de criar o módulo na primeira tarefa, vamos adicioná-lo ao aplicativo.

Implantar no aplicativo

Primeiro, implante o módulo no aplicativo. Aqui, você tem várias opções:

  • Copie o arquivo de origem que contém o módulo para o diretório /App_Code do aplicativo. Isso não requer a compilação do módulo — o ASP.NET compila e carrega automaticamente o tipo de módulo quando o aplicativo é iniciado. Basta salvar esse código-fonte como BasicAuthenticationModule.cs dentro do diretório /App_Code do seu aplicativo. Faça isso se você não se sentir confortável com as outras etapas.

  • Compile o módulo em um assembly e solte esse assembly no diretório /BIN do aplicativo. Essa é a opção mais típica se você quiser apenas que esse módulo esteja disponível para esse aplicativo e não desejar enviar a origem do módulo com seu aplicativo. Compile o arquivo de origem do módulo executando o seguinte a partir de um prompt de linha de comando:

    <PATH_TO_FX_SDK>csc.exe /out:BasicAuthenticationModule.dll /target:library BasicAuthenticationModule.cs

    Onde <PATH_TO_FX_SDK> é o caminho para o SDK do .NET Framework que contém o compilador CSC.EXE.

  • Compile o módulo em um assembly com um nome forte e registre esse assembly no GAC. Essa é uma boa opção se você quiser que vários aplicativos em sua máquina usem esse módulo. Para saber mais sobre como criar assemblies com nomes fortes, confira Criar e usar assemblies com nomes fortes.

Antes de fazer alterações de configuração no arquivo web.config do aplicativo, devemos desbloquear algumas das seções de configuração que estão bloqueadas no nível do servidor por padrão. Execute o seguinte em um prompt de comandos com privilégios elevados (inicie >, clique com o botão direito do mouse em Cmd.exe e escolha “Executar como Administrador”):

%windir%\system32\inetsrv\APPCMD.EXE unlock config /section:windowsAuthentication
%windir%\system32\inetsrv\APPCMD.EXE unlock config /section:anonymousAuthentication

Depois de executar esses comandos, você poderá definir essas seções de configuração no arquivo web.config do aplicativo.

Configure seu módulo para ser executado no aplicativo. Comece criando um novo arquivo web.config, que conterá a configuração necessária para habilitar e usar o novo módulo. Comece adicionando o texto abaixo e salvando-o na raiz do seu aplicativo (%systemdrive%\inetpub\wwwroot\web.config se estiver usando o aplicativo raiz no Site Padrão).

<configuration> 
    <system.webServer> 
        <modules> 
        </modules> 
        <security> 
            <authentication> 
                <windowsAuthentication enabled="false"/> 
                <anonymousAuthentication enabled="false"/> 
            </authentication> 
        </security> 
    </system.webServer> 
</configuration>

Antes de habilitar o novo módulo de autenticação básica, desabilite todos os outros módulos de autenticação do IIS. Por padrão, somente a autenticação do Windows e a autenticação anônima estão habilitadas. Como não queremos que o navegador tente autenticar com suas credenciais do Windows ou permita usuários anônimos, desabilitamos o módulo de autenticação do Windows e o módulo de autenticação anônima.

Agora habilite o módulo adicionando-o à lista de módulos carregados pelo nosso aplicativo. Abra web.config mais uma vez e adicione a entrada dentro da tag <modules>.

<add name="MyBasicAuthenticationModule" type="IIS7Demos.BasicAuthenticationModule" />

Você também pode implantar o módulo usando a Ferramenta de Administração do IIS ou a ferramenta de linha de comando APPCMD.EXE.

O conteúdo final do arquivo web.config do aplicativo após essas alterações é fornecido no Apêndice B.

Parabéns, você terminou de configurar o módulo de autenticação básica personalizado.

Vamos testá-lo! Abra o Internet Explorer e faça uma solicitação ao aplicativo na seguinte URL:

http://localhost/

Você deve ver a caixa de diálogo de logon de autenticação básica. Digite “test” no campo “Nome de usuário:” e “test” no campo “Senha:” para ter acesso. Observe que se você copiar HTML, JPG ou qualquer outro conteúdo para seu aplicativo, eles também serão protegidos pelo seu novo BasicAuthenticationModule.

Resumo

Neste artigo, você aprendeu a desenvolver e implantar um módulo gerenciado personalizado para um aplicativo e habilitar esse módulo para fornecer serviços para todas as solicitações ao aplicativo.

Você também testemunhou o poder do desenvolvimento de componentes de servidor em código gerenciado. Isso permitiu desenvolver um serviço de autenticação básico que é desacoplado do armazenamento de credenciais do Windows.

Se quiser se aventurar, configure este módulo para aproveitar o poder dos serviços de aplicativo de associação do ASP.NET 2.0 para dar suporte a repositórios de credenciais conectáveis. Confira o Apêndice C para obter mais informações.

Encontre muitos recursos e dicas sobre como escrever módulos do IIS no blog, http://www.mvolo.com/, e baixe módulos do IIS existentes para seus aplicativos. Para obter alguns exemplos, confira Redirecionamento de solicitações para seu aplicativo com o módulo HttpRedirection, Listas de diretórios interessantes para seu site do IIS com DirectoryListingModule e Exibindo ícones de arquivo bonitos em seus aplicativos ASP.NET com o IconHandler.

Apêndice A: Código-Fonte do Módulo de Autenticação Básica

Basta salvar esse código-fonte como BasicAuthenticationModule.cs dentro do diretório /App_Code do seu aplicativo.

Observação

Se você estiver usando o bloco de notas, defina Salvar como: todos os arquivos para evitar salvar o arquivo como BasicAuthenticationModule.cs.txt.

#region Using directives
using System;
using System.Collections;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Security.Principal;
using System.IO;
#endregion
 
namespace IIS7Demos
{
    /// 
    /// This module performs basic authentication. 
    /// For details on basic authentication see RFC 2617. 
    /// 
    /// The basic operational flow is: 
    /// 
    ///     On AuthenticateRequest: 
    ///         extract the basic authentication credentials 
    ///         verify the credentials 
    ///         if succesfull, create the user principal with these credentials 
    /// 
    ///     On SendResponseHeaders: 
    ///         if the request is being rejected with an unauthorized status code (401), 
    ///         add the basic authentication challenge to trigger basic authentication. 
    ///       
    /// 

    public class BasicAuthenticationModule : IHttpModule
    {
        #region member declarations
        public const String     HttpAuthorizationHeader = "Authorization";  // HTTP1.1 Authorization header 
        public const String     HttpBasicSchemeName = "Basic"; // HTTP1.1 Basic Challenge Scheme Name 
        public const Char       HttpCredentialSeparator = ':'; // HTTP1.1 Credential username and password separator 
        public const int        HttpNotAuthorizedStatusCode = 401; // HTTP1.1 Not authorized response status code 
        public const String     HttpWWWAuthenticateHeader = "WWW-Authenticate"; // HTTP1.1 Basic Challenge Scheme Name 
        public const String     Realm = "demo"; // HTTP.1.1 Basic Challenge Realm 
        #endregion

        #region Main Event Processing Callbacks
        public void AuthenticateUser(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
            String userName = null;
            String password = null;
            String realm = null;
            String authorizationHeader = context.Request.Headers[HttpAuthorizationHeader];

            // 
            //  Extract the basic authentication credentials from the request 
            // 
            if (!ExtractBasicCredentials(authorizationHeader, ref userName, ref password))
                return;
            // 
            // Validate the user credentials 
            // 
            if (!ValidateCredentials(userName, password, realm))
               return;

            // 
            // Create the user principal and associate it with the request 
            // 
            context.User = new GenericPrincipal(new GenericIdentity(userName), null);
        }

        public void IssueAuthenticationChallenge(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            // 
            // Issue a basic challenge if necessary 
            // 

            if (context.Response.StatusCode == HttpNotAuthorizedStatusCode)
            {
                context.Response.AddHeader(HttpWWWAuthenticateHeader, "Basic realm =\"" + Realm + "\"");
            }
        }
        #endregion

        #region Utility Methods
        protected virtual bool ValidateCredentials(String userName, String password, String realm)
        {
            // 
            //  Validate the credentials using Membership (refault provider) 
            // 
            // NOTE: Membership is commented out for clarity reasons.   
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
            // WARNING: DO NOT USE THE CODE BELOW IN PRODUCTION 
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
            // return Membership.ValidateUser(userName, password); 
            if (userName.Equals("test") && password.Equals("test"))
            {
                return true;
            }
            else 
            {
                return false;
            }    
        }
      
        protected virtual bool ExtractBasicCredentials(String authorizationHeader, ref String username, ref String password)
        {
            if ((authorizationHeader == null) || (authorizationHeader.Equals(String.Empty)))
               return false;
            String verifiedAuthorizationHeader = authorizationHeader.Trim();
            if (verifiedAuthorizationHeader.IndexOf(HttpBasicSchemeName) != 0)     
                return false;

            // get the credential payload 
            verifiedAuthorizationHeader = verifiedAuthorizationHeader.Substring(HttpBasicSchemeName.Length, verifiedAuthorizationHeader.Length - HttpBasicSchemeName.Length).Trim();
           // decode the base 64 encoded credential payload 
            byte[] credentialBase64DecodedArray = Convert.FromBase64String(verifiedAuthorizationHeader);
            UTF8Encoding encoding = new UTF8Encoding();
            String decodedAuthorizationHeader = encoding.GetString(credentialBase64DecodedArray, 0, credentialBase64DecodedArray.Length);

            // get the username, password, and realm 
            int separatorPosition = decodedAuthorizationHeader.IndexOf(HttpCredentialSeparator);

           if (separatorPosition <= 0)
              return false;
            username = decodedAuthorizationHeader.Substring(0, separatorPosition).Trim();
           password = decodedAuthorizationHeader.Substring(separatorPosition + 1, (decodedAuthorizationHeader.Length - separatorPosition - 1)).Trim();

            if (username.Equals(String.Empty) || password.Equals(String.Empty))
               return false;

           return true;
        }
        #endregion

        #region IHttpModule Members
        public void Init(HttpApplication context)
        {
            // 
            // Subscribe to the authenticate event to perform the 
            // authentication. 
            // 
            context.AuthenticateRequest += new 
                               EventHandler(this.AuthenticateUser);
            // 
            // Subscribe to the EndRequest event to issue the 
            // challenge if necessary. 
            // 
            context.EndRequest += new 
                               EventHandler(this.IssueAuthenticationChallenge);
        }
        public void Dispose()
        {
            // 
            // Do nothing here 
            // 
        }
        #endregion

    }
}

Apêndice B: Web.config para módulo de autenticação básico

Salve esta configuração como arquivo web.config na raiz do seu aplicativo:

<configuration> 
    <system.webServer> 
      <modules> 
           <add name="MyBasicAuthenticationModule" type="IIS7Demos.BasicAuthenticationModule" /> 
      </modules> 
      <security> 
         <authentication> 
          <windowsAuthentication enabled="false"/> 
             <anonymousAuthentication enabled="false"/> 
         </authentication> 
      </security> 
    </system.webServer> 
</configuration>

Apêndice C: Configurando a associação

O serviço de associação ASP.NET 2.0 permite que os aplicativos implementem rapidamente a validação de credenciais e o gerenciamento de usuários exigidos pela maioria dos esquemas de autenticação e controle de acesso. A associação isola o código do aplicativo da implementação real do repositório de credenciais e fornece várias opções para integração com repositórios de credenciais existentes.

Para aproveitar a associação para este exemplo de módulo, remova a marca de comentário de uma chamada para Membership.ValidateUser dentro do método ValidateCredentials e configure um provedor de associação para seu aplicativo. Para obter mais informações sobre como configurar a associação, confira Configurando um aplicativo ASP.NET para usar associação.