Condividi tramite


Sviluppo di un modulo con .NET

di Mike Volodarsky

Introduzione

IIS 7.0 e versioni successive consente di estendere il server in base ai moduli sviluppati in due modi:

  • Uso del codice gestito e delle API di estendibilità del server ASP.NET
  • Uso del codice nativo e delle API di estendibilità del server nativo IIS

In passato, ASP.NET moduli erano limitati in funzionalità, poiché la pipeline di elaborazione delle richieste di ASP.NET era separata dalla pipeline di richiesta del server principale.

In IIS i moduli gestiti diventano praticamente potenti come moduli nativi con l'architettura della pipeline integrata. Più importante, i servizi forniti dai moduli gestiti possono ora essere applicati a tutte le richieste al server, non solo alle richieste di ASP.NET contenuto come le pagine ASPX. I moduli gestiti vengono configurati e gestiti in modo coerente con i moduli nativi e possono essere eseguiti nelle stesse fasi di elaborazione e ordinamento dei moduli nativi. Infine, i moduli gestiti possono eseguire un set più ampio di operazioni per modificare l'elaborazione delle richieste tramite diverse API di ASP.NET aggiunte e migliorate.

Questo articolo illustra l'estensione del server con un modulo gestito per aggiungere la possibilità di eseguire l'autenticazione di base in un archivio di credenziali arbitrario, ad esempio l'infrastruttura di credenziali basata su provider nel sistema di appartenenza ASP.NET 2.0.

Ciò consente di sostituire il supporto di autenticazione di base predefinito in IIS, associato all'archivio credenziali di Windows, con uno che supporta archivi di credenziali arbitrari o uno dei provider di appartenenze esistenti forniti con ASP.NET 2.0 come SQL Server, SQL Express o Active Directory.

Questo articolo esamina le attività seguenti:

  • Sviluppo di un modulo gestito usando le API di ASP.NET
  • Distribuzione di un modulo gestito nel server

Per altre informazioni sulle nozioni di base sullo sviluppo di moduli e gestori IIS, vedere Sviluppo di moduli e gestori IIS7 con .NET Framework.

È anche possibile trovare numerose risorse e suggerimenti sulla scrittura di moduli IIS nel blog, http://www.mvolo.com/, nonché scaricare moduli IIS esistenti per le applicazioni. Per alcuni esempi, vedere Reindirizzamento delle richieste all'applicazione con il modulo HttpRedirection, elenchi di directory ben presentati per il sito Web IIS con DirectoryListingModule e Visualizzazione di icone di file attraenti nelle applicazioni ASP.NET usando IconHandler.

Annotazioni

Il codice fornito in questo articolo è scritto in C#.

Prerequisiti

Per seguire la procedura descritta in questo documento, è necessario che siano installate le funzionalità IIS seguenti:

ASP.NET

Installare ASP.NET tramite il Pannello di controllo di Windows Vista. Selezionare "Programmi" - "Attiva o disattiva funzionalità di Windows". Aprire quindi "Internet Information Services" - "World Wide Web Services" - "Application Development Features" e selezionare "ASP.NET".

Se si dispone di una build di Windows Server® 2008, aprire "Server Manager" - "Ruoli" e selezionare "Server Web (IIS)". Fare clic su "Aggiungi servizi del ruolo". In "Sviluppo di applicazioni" selezionare "ASP.NET".

Informazioni generali sull'autenticazione di base

L'autenticazione di base è uno schema di autenticazione definito nel protocollo HTTP.1 (RFC 2617). Usa un meccanismo standard basato su sfida che funziona come segue a livello generale:

  • Browser effettua una richiesta a un URL senza credenziali
  • Se il server richiede l'autenticazione per tale URL, risponde con un messaggio di accesso negato 401 e include un'intestazione che indica che lo schema di autenticazione di base è supportato
  • Browser riceve la risposta e, se configurata, chiederà all'utente un nome utente/password che includerà in testo normale all'interno di un'intestazione della richiesta per la richiesta successiva all'URL
  • Il server riceve il nome utente/password all'interno di un'intestazione e li usa per l'autenticazione

Annotazioni

Anche se una descrizione dettagliata di questo protocollo di autenticazione non rientra nell'ambito di questo articolo, vale la pena menzionare che lo schema di autenticazione di base richiede che SSL sia sicuro, in quanto invia il nome utente/password in testo normale.

IIS include il supporto per l'autenticazione di base per gli account di Windows archiviati nell'archivio account locale o Active Directory per gli account di dominio. Vogliamo consentire al nostro utente di autenticarsi utilizzando l'autenticazione di base, ma convalidare le credenziali utilizzando il servizio Membership ASP.NET 2.0. In questo modo è possibile archiviare le informazioni utente in un'ampia gamma di provider di appartenenze esistenti, ad esempio SQL Server, senza essere associati agli account di Windows.

Attività 1: Sviluppo di un modulo con .NET

In questa attività viene esaminato lo sviluppo di un modulo di autenticazione che supporta lo schema di autenticazione di base HTTP.1. Questo modulo è stato sviluppato usando lo standard ASP.NET modello di modulo disponibile a partire da ASP.NET v1.0. Questo stesso modello viene usato per compilare ASP.NET moduli che estendono il server IIS. Infatti, i moduli di ASP.NET esistenti scritti per le versioni precedenti di IIS possono essere usati in IIS e sfruttano una migliore integrazione ASP.NET per offrire maggiore potenza alle applicazioni Web che le usano.

Annotazioni

Il codice completo per il modulo è disponibile nell'Appendice A.

Un modulo gestito è una classe .NET che implementa l'interfaccia System.Web.IHttpModule . La funzione primaria di questa classe consiste nel registrare uno o più eventi che si verificano all'interno della pipeline di elaborazione delle richieste IIS e quindi eseguire alcune operazioni utili quando IIS richiama i gestori eventi del modulo per tali eventi.

Consente di creare un nuovo file di origine denominato "BasicAuthenticationModule.cs" e di creare la classe del modulo (il codice sorgente completo è disponibile nell'Appendice A):

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

La funzione primaria del metodo Init consiste nel collegare i metodi del gestore eventi del modulo agli eventi della pipeline di richiesta appropriati. La classe del modulo fornisce i metodi di gestione degli eventi e implementa le funzionalità desiderate fornite dal modulo. Questo argomento viene illustrato in modo più dettagliato.

Il metodo Dispose viene usato per pulire qualsiasi stato del modulo quando l'istanza del modulo viene rimossa. In genere non viene implementato a meno che il modulo non usi risorse specifiche che richiedono il rilascio.

Init()

Dopo aver creato la classe, il passaggio successivo consiste nell'implementare il metodo Init . L'unico requisito consiste nel registrare il modulo per uno o più eventi della pipeline di richieste. Collegare i metodi del modulo, che seguono la firma del delegato System.EventHandler, agli eventi della pipeline desiderati esposti nell'istanza di System.Web.HttpApplication fornita:

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);
}

Il metodo AuthenticateUser viene richiamato su ogni richiesta durante l'evento AuthenticateRequest . Viene utilizzato per autenticare l'utente in base alle informazioni sulle credenziali presenti nella richiesta.

Il metodo IssueAuthenticationChallenge viene richiamato su ogni richiesta durante l'evento EndRequest . È responsabile dell'emissione di una richiesta di autenticazione di base al client ogni volta che il modulo di autorizzazione rifiuta una richiesta ed è necessaria l'autenticazione.

AuthenticateUser()

Implementare il metodo AuthenticateUser . Questo metodo esegue le operazioni seguenti:

  • Estrarre le credenziali di base, se presenti, dalle intestazioni della richiesta in ingresso. Per visualizzare l'implementazione di questo passaggio, vedere il metodo di utilità ExtractBasicAuthenticationCredentials .
  • Tenta di convalidare le credenziali fornite tramite Membership (utilizzando il provider di membership predefinito configurato). Per visualizzare l'implementazione di questo passaggio, vedere il metodo dell'utilità ValidateCredentials .
  • Crea un'entità utente che identifica l'utente se l'autenticazione ha esito positivo e la associa alla richiesta.

Al termine dell'elaborazione, se il modulo è riuscito a ottenere e convalidare le credenziali utente, produrrà un'entità utente autenticata che altri moduli e codice dell'applicazione usino successivamente nelle decisioni relative al controllo di accesso. Ad esempio, il modulo di autorizzazione URL esamina l'utente nell'evento della pipeline successivo per applicare le regole di autorizzazione configurate dall'applicazione.

IssueAuthenticationChallenge()

Implementare il metodo IssueAuthenticationChallenge . Questo metodo esegue le operazioni seguenti:

  • Controllare il codice di stato della risposta per determinare se la richiesta è stata rifiutata.
  • In tal caso, rilasciare un'intestazione di richiesta di autenticazione di base alla risposta per attivare l'autenticazione del client.

Metodi di utilità

Implementare i metodi di utilità usati dal modulo, tra cui:

  • ExtractBasicAuthenticationCredentials. Questo metodo estrae le credenziali di autenticazione di base dall'intestazione Autorizza richiesta, come specificato nello schema di autenticazione di base.
  • ValidateCredentials. Questo metodo tenta di convalidare le credenziali utente usando l'appartenenza. L'API di appartenenza astrae l'archivio delle credenziali sottostante e consente di configurare le implementazioni dell'archivio credenziali aggiungendo/rimuovendo provider di appartenenza tramite la configurazione.

Annotazioni

In questo esempio la convalida dell'appartenenza viene impostata come commento e il modulo controlla semplicemente se il nome utente e la password sono entrambi uguali alla stringa "test". Questa operazione viene eseguita per maggiore chiarezza e non è destinata alle distribuzioni di produzione. Si è invitati a abilitare la convalida delle credenziali basate sull'appartenenza semplicemente annullando il commento del codice di appartenenza all'interno di ValidateCredentials e configurando un provider di appartenenze per l'applicazione. Per altre informazioni, vedere Appendice C.

Attività 2: Distribuire il modulo nell'applicazione

Dopo aver creato il modulo nella prima attività, aggiungerlo successivamente all'applicazione.

Distribuire nell'applicazione

Prima di tutto, distribuisci il modulo all'applicazione. In questo caso sono disponibili diverse opzioni:

  • Copiare il file di origine contenente il modulo nella directory /App_Code dell'applicazione. Ciò non richiede la compilazione del modulo: ASP.NET compila automaticamente e carica il tipo di modulo all'avvio dell'applicazione. È sufficiente salvare questo codice sorgente come BasicAuthenticationModule.cs all'interno della directory /App_Code dell'applicazione. Fai questo se non ti senti a tuo agio con gli altri passaggi.

  • Compilare il modulo in un assembly ed eliminare questo assembly nella directory /BIN dell'applicazione. Questa è l'opzione più tipica se si vuole che questo modulo sia disponibile solo per questa applicazione e non si vuole spedire l'origine del modulo con l'applicazione. Compilare il file di origine del modulo eseguendo il comando seguente da un prompt della riga di comando:

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

    Dove <PATH_TO_FX_SDK> è il percorso di .NET Framework SDK che contiene il compilatore CSC.EXE.

  • Compilare il modulo in un assembly con nome sicuro e registrare questo assembly nella GAC. Questa è un'opzione valida se si desidera che più applicazioni nel computer usino questo modulo. Per altre informazioni sulla creazione di assembly con nome sicuro, vedere Creare e usare assembly con nome sicuro.

Prima di apportare modifiche alla configurazione nel file di web.config dell'applicazione, è necessario sbloccare alcune delle sezioni di configurazione bloccate a livello di server per impostazione predefinita. Eseguire il comando seguente da un prompt dei comandi con privilegi elevati (fare > clic con il pulsante destro del mouse su Cmd.exe e scegliere "Esegui come amministratore"):

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

Dopo aver eseguito questi comandi, sarà possibile definire queste sezioni di configurazione nel file di web.config dell'applicazione.

Configurare il modulo per l'esecuzione nell'applicazione. Per iniziare, creare un nuovo file web.config, che conterrà la configurazione necessaria per abilitare e usare il nuovo modulo. Per iniziare, aggiungere il testo seguente e salvarlo nella radice dell'applicazione (%systemdrive%\inetpub\wwwroot\web.config se si usa l'applicazione radice nel sito Web predefinito).

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

Prima di abilitare il nuovo modulo di autenticazione di base, disabilitare tutti gli altri moduli di autenticazione IIS. Per impostazione predefinita, sono abilitati solo l'autenticazione di Windows e l'autenticazione anonima. Poiché non vogliamo che il browser tenti di eseguire l'autenticazione con le credenziali di Windows o consenta utenti anonimi, disattiviamo sia il modulo di Autenticazione di Windows sia il modulo di Autenticazione anonima.

Abilitare ora il modulo aggiungendolo all'elenco dei moduli caricati dall'applicazione. Aprire di nuovo web.config e aggiungere la voce all'interno del <modules> tag

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

È anche possibile distribuire il modulo usando lo Strumento di amministrazione IIS o lo strumento da riga di comando APPCMD.EXE.

Il contenuto finale del file di web.config dell'applicazione dopo queste modifiche viene fornito nell'Appendice B.

La configurazione del modulo di autenticazione di base personalizzato è stata completata.

Proviamolo! Aprire Internet Explorer e effettuare una richiesta all'applicazione all'URL seguente:

http://localhost/

Verrà visualizzata la finestra di dialogo di accesso di autenticazione di base. Immettere "test" nel campo "Nome utente:" e "test" nel campo "Password:" per ottenere l'accesso. Si noti che se si copia HTML, JPG o qualsiasi altro contenuto nell'applicazione, anche essi saranno protetti dal nuovo BasicAuthenticationModule.

Sommario

In questo articolo si è appreso come sviluppare e distribuire un modulo gestito personalizzato per un'applicazione e abilitare tale modulo per fornire servizi per tutte le richieste all'applicazione.

Anche tu hai assistito alla potenzialità dello sviluppo di componenti server nel codice gestito. Ciò ha consentito lo sviluppo di un servizio di autenticazione di base separato dall'archiviazione delle credenziali di Windows.

Se si è avventurosi, configurare questo modulo per sfruttare la potenza dei servizi dell'applicazione di appartenenza ASP.NET 2.0 per supportare gli archivi di credenziali collegabili. Per altre informazioni, vedere Appendice C.

Trovare molte risorse e suggerimenti sulla scrittura di moduli IIS nel blog, http://www.mvolo.com/, nonché scaricare i moduli IIS esistenti per le applicazioni. Per alcuni esempi, vedere Reindirizzamento delle richieste all'applicazione con il modulo HttpRedirection, elenchi di directory ben presentati per il sito Web IIS con DirectoryListingModule e Visualizzazione di icone di file attraenti nelle applicazioni ASP.NET usando IconHandler.

Appendice A: Codice sorgente del modulo di autenticazione di base

Salvare questo codice sorgente come BasicAuthenticationModule.cs all'interno della directory /App_Code per distribuirlo rapidamente nell'applicazione.

Annotazioni

Se utilizzi Blocco note, assicurati di impostare l'opzione Salva con nome su Tutti i file per evitare di salvare il file come 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

    }
}

Appendice B: Web.config per il modulo di autenticazione di base

Salvare questa configurazione come file web.config nella radice dell'applicazione:

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

Appendice C: Configurazione dell'appartenenza

Il servizio di appartenenza ASP.NET 2.0 consente alle applicazioni di implementare rapidamente la convalida delle credenziali e la gestione degli utenti richiesta dalla maggior parte degli schemi di autenticazione e controllo di accesso. L'appartenenza isola il codice dell'applicazione dall'implementazione effettiva dell'archivio credenziali e offre una serie di opzioni per l'integrazione con gli archivi credenziali esistenti.

Per sfruttare il Membership per questo esempio di modulo, rimuovere il commento da una chiamata a Membership.ValidateUser all'interno del metodo ValidateCredentials e configurare un provider Membership per l'applicazione. Per altre informazioni sulla configurazione dell'appartenenza, vedere Configurazione di un'applicazione ASP.NET per l'uso dell'appartenenza.