Partager via


Développement d'un module à l'aide de .NET

Auteur : Mike Volodarsky

Introduction

IIS 7.0 et versions ultérieures permet d'étendre le serveur par des modules développés de deux manières :

  • Utilisation du code managé et des API d'extensibilité du serveur ASP.NET
  • Utilisation du code natif et des API d'extensibilité du serveur natif IIS

Par le passé, ASP.NET modules étaient limités en fonctionnalités, car le pipeline de traitement des demandes ASP.NET était séparé du pipeline de requête principal du serveur.

Dans IIS, les modules managés deviennent pratiquement aussi puissants que les modules natifs avec l'architecture de pipeline intégré. Plus important encore, les services fournis par les modules managés peuvent désormais être appliqués à toutes les requêtes au serveur, et non pas seulement aux demandes d'ASP.NET contenu comme les pages ASPX. Les modules managés sont configurés et managés de manière cohérente avec les modules natifs et peuvent s'exécuter dans les mêmes étapes de traitement et classements que les modules natifs. Enfin, les modules managés peuvent effectuer un ensemble plus large d'opérations pour manipuler le traitement des demandes par le biais de plusieurs API ajoutées et améliorées ASP.NET.

Le présent article illustre l'extension du serveur avec un module managé afin d'ajouter la possibilité d'effectuer l'authentification de base sur un magasin d'informations d'identification arbitraire, comme l'infrastructure d'informations d'identification basée sur le fournisseur dans le système d'appartenance ASP.NET 2.0.

Il est ainsi possible de remplacer le support d'authentification de base intégré à IIS, qui est lié au magasin d'informations de Windows, par un support qui prend en charge des magasins d'informations arbitraires ou n'importe lequel des fournisseurs d'appartenances existants livrés avec ASP.NET 2.0, tels que SQL Server, SQL Express ou Active Directory.

Le présent article examine les tâches suivantes :

  • Développement d'un module managé à l'aide d'API ASP.NET
  • Déploiement d'un module managé sur le serveur

Pour en savoir plus sur les principes de base du développement de modules et de gestionnaires IIS, consultez Développement de modules et de gestionnaires IIS7 avec le .NET Framework.

Vous trouverez également de nombreuses ressources et conseils sur l'écriture de modules IIS sur le blog, http://www.mvolo.com/ainsi que le téléchargement des modules IIS existants pour vos applications. Pour obtenir des exemples, consultez Rediriger des demandes vers votre application avec le module HttpRedirection, Nice recherche les listes de répertoires pour votre site Web IIS avec DirectoryListingModule et affiche des icônes de fichiers jolies dans vos applications ASP.NET avec IconHandler.

Remarque

Le code fourni dans cet article est écrit en C#.

Prérequis

Pour suivre les étapes décrites dans ce document, vous devez installer les fonctionnalités IIS suivantes :

ASP.NET

Installez ASP.NET par le biais du panneau de configuration Windows Vista. Sélectionnez « Programmes » : « Activer ou désactiver les fonctionnalités Windows ». Ouvrez ensuite « Internet Information Services », « World Wide Web Services », « Fonctionnalités de développement d'applications » et cochez « ASP.NET ».

Si vous disposez d'une build Windows Server® 2008, ouvrez « Gestionnaire de serveur », « Rôles », puis sélectionnez « Serveur Web (IIS) ». Cliquez sur « Ajouter des services de rôle ». Sous « Développement d'applications », cochez « ASP.NET ».

Informations générales sur l'authentification de base

L'authentification de base est un schéma d'authentification défini dans le protocole HTTP.1 (RFC 2617). Il utilise un mécanisme standard basé sur des défis qui fonctionne comme suit à un niveau élevé :

  • Le navigateur effectue une requête vers une URL sans informations d'identification
  • Si le serveur nécessite une authentification pour cette URL, il répond avec un message 401 Accès refusé et inclut un en-tête qui indique que le schéma d'authentification de base est pris en charge
  • Le navigateur reçoit la réponse, et s'il est configuré, invite l'utilisateur à saisir un nom d'utilisateur/mot de passe qu'il inclut en texte brut à l'intérieur d'un en-tête de demande pour la requête suivante à l'URL
  • Le serveur reçoit le nom d'utilisateur/mot de passe à l'intérieur d'un en-tête et les utilise pour l'authentification

Remarque

Bien qu'une discussion détaillée de ce protocole d'authentification ne soit pas dans le cadre de cet article, il convient de mentionner que le schéma d'authentification de base exige que SSL soit sécurisé, car il envoie le nom d'utilisateur/mot de passe en texte brut.

IIS inclut la prise en charge de l'authentification de base sur les comptes Windows stockés dans le magasin de comptes local ou Active Directory pour les comptes de domaine. Nous voulons permettre à notre utilisateur de s'authentifier à l'aide de l'authentification de base, mais de valider les informations d'identification à l'aide de ASP.NET service d'appartenance 2.0 à la place. Des informations utilisateur peuvent ainsi être stockées dans divers fournisseurs d'appartenance existants, tels que SQL Server, sans être liées à des comptes Windows.

Tâche 1 : développement d'un module à l'aide de .NET

Cette tâche consiste à examiner le développement d'un module d'authentification qui prend en charge le schéma d'authentification de base HTTP.1. Ce module a été développé à l'aide du modèle de module standard ASP.NET disponible depuis ASP.NET version 1.0. Ce même modèle est utilisé pour générer des modules ASP.NET qui étendent le serveur IIS. En fait, les modules de ASP.NET existants écrits pour les versions précédentes d'IIS peuvent être utilisés sur IIS et tirer parti d'une meilleure intégration ASP.NET pour fournir plus de puissance aux applications Web qui les utilisent.

Remarque

Le code complet du module est fourni dans l'annexe A.

Un module managé est une classe .NET qui implémente l'interface System.Web.IHttpModule. La fonction principale de cette classe consiste à s'inscrire à un ou plusieurs événements qui se produisent dans le pipeline de traitement des demandes IIS, puis à effectuer un travail utile lorsque IIS appelle les gestionnaires d'événements du module pour ces événements.

Permet de créer un fichier source nommé « BasicAuthenticationModule.cs » et de créer la classe de module (le code source complet est fourni dans l'annexe A) :

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

La fonction principale de la méthode Init consiste à connecter les méthodes du gestionnaire d'événements du module aux événements de pipeline de requête appropriés. La classe du module fournit les méthodes de handle d'événements et implémente les fonctionnalités souhaitées fournies par le module. Cet aspect est abordé plus en détail.

La méthode Dispose est utilisée pour nettoyer tout état de module lorsque l'instance de module est ignorée. Il n'est généralement pas implémenté, sauf si le module utilise des ressources spécifiques qui doivent être libérées.

Init()

Après avoir créé la classe, l'étape suivante consiste à implémenter la méthode Init. La seule exigence est d'inscrire le module pour un ou plusieurs événements de pipeline de requête. Associez les méthodes de module, qui suivent la signature de délégué System.EventHandler, aux événements de pipeline souhaités exposés sur l'instance System.Web.HttpApplication fournie :

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

La méthode AuthenticateUser est appelée sur chaque requête pendant l'événement AuthenticateRequest. Nous l'utilisons pour authentifier l'utilisateur en fonction des informations d'identification présentes dans la demande.

La méthode IssueAuthenticationChallenge est appelée sur chaque requête pendant l'événement EndRequest. Il est responsable de l'émission d'un défi d'authentification de base au client chaque fois que le module d'autorisation rejette une demande et que l'authentification est nécessaire.

AuthenticateUser()

Adoptez la méthode AuthenticateUser. Cette méthode consiste à effectuer les opérations suivantes :

  • Extrayez les informations d'identification de base si elles sont présentes à partir des en-têtes de requête entrants. Pour voir l'implémentation de cette étape, consultez la méthode utilitaire ExtractBasicAuthenticationCredentials.
  • Tente de valider les informations d'identification fournies par le biais de l'appartenance (à l'aide du fournisseur d'appartenance par défaut configuré). Pour voir l'implémentation de cette étape, consultez la méthode de l'utilitaire ValidateCredentials.
  • Crée un principal d'utilisateur identifiant l'utilisateur si l'authentification réussit et l'associe à la demande.

À la fin de ce traitement, si le module a réussi à obtenir et valider les informations d'identification de l'utilisateur, il produit un principal d'utilisateur authentifié que d'autres modules et code d'application utilisent ultérieurement dans les décisions de contrôle d'accès. Par exemple, le module d'autorisation d'URL examine l'utilisateur dans l'événement de pipeline suivant afin d'appliquer les règles d'autorisation configurées par l'application.

IssueAuthenticationChallenge()

Implémentez la méthode IssueAuthenticationChallenge. Cette méthode consiste à effectuer les opérations suivantes :

  • Vérifiez le code d'état de la réponse pour déterminer si cette demande a été rejetée.
  • Dans ce cas, émettez un en-tête de défi d'authentification de base à la réponse pour déclencher l'authentification du client.

Méthodes utilitaires

Adoptez les méthodes utilitaires utilisées par le module, notamment :

  • ExtractBasicAuthenticationCredentials. Cette méthode extrait les informations d'identification d'authentification de base de l'en-tête de demande d'autorisation, comme spécifié dans le schéma d'authentification de base.
  • ValidateCredentials. Cette méthode tente de valider les informations d'identification de l'utilisateur à l'aide de l'appartenance. L'API Appartenance extrait le magasin d'informations d'identification sous-jacent et permet la configuration des implémentations du magasin d'informations d'identification en ajoutant/supprimant des fournisseurs d'appartenance par le biais de la configuration.

Remarque

Dans cet exemple, la validation d'appartenance est commentée, et au lieu de cela, le module vérifie simplement si le nom d'utilisateur et le mot de passe sont tous deux égaux à la chaîne « test ». Cette opération est effectuée pour plus de clarté et n'est pas destinée aux déploiements de production. Vous êtes invité à activer la validation des informations d'identification basées sur l'appartenance en désactivant simplement le code d'appartenance dans ValidateCredentials et en configurant un fournisseur d'appartenances pour votre application. Pour en savoir plus, consultez l'Annexe C.

Tâche 2 : déployer le module sur l'application

Après avoir créé le module dans la première tâche, nous l'ajoutons ensuite à l'application.

Déployer sur l'application

Tout d'abord, déployez le module sur l'application. À ce niveau, vous disposez de plusieurs options :

  • Copiez le fichier source contenant le module dans le répertoire /App_Code de l'application. Pour ce faire, il n'est pas nécessaire que le module soit compilé : ASP.NET compile et charge automatiquement le type de module au démarrage de l'application. Enregistrez simplement ce code source sous BasicAuthenticationModule.cs dans le répertoire /App_Code de votre application. Procédez ainsi si les autres étapes ne vous conviennent pas.

  • Compilez le module dans un assembly et supprimez cet assembly dans le répertoire /BIN de l'application. Il s'agit de l'option la plus classique si vous souhaitez uniquement que ce module soit disponible pour cette application et que vous ne souhaitez pas expédier la source du module avec votre application. Compilez le fichier source du module en exécutant ce qui suit à partir d'une invite de ligne de commande :

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

    <PATH_TO_FX_SDK> se trouve le chemin d'accès au Kit de développement logiciel (SDK) .NET Framework qui contient le compilateur CSC.EXE.

  • Compilez le module dans un assembly à nom fort et inscrivez cet assembly dans le GAC. Il s'agit d'une bonne option si vous souhaitez que plusieurs applications sur votre ordinateur utilisent ce module. Pour en savoir plus sur la création d'assemblys à nom fort, consultez Créer et utiliser des assemblys avec des noms forts.

Avant d'apporter des modifications de configuration dans le fichier Web.config de l'application, nous devons déverrouiller certaines sections de configuration verrouillées au niveau du serveur par défaut. Exécutez ce qui suit à partir d'une invite de commandes Avec élévation de privilèges (commencez par >cliquer avec le bouton droit sur Cmd.exe et choisissez « Exécuter en tant qu'administrateur ») :

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

Après avoir exécuté ces commandes, vous pourrez définir ces sections de configuration dans le fichier Web.config de votre application.

Configurez votre module pour qu'il s'exécute dans l'application. Commencez par créer un fichier Web.config, qui contiendra la configuration nécessaire pour activer et utiliser le nouveau module. Commencez par ajouter le texte ci-dessous et l'enregistrer à la racine de votre application (%systemdrive%\inetpub\wwwroot\web.config si vous utilisez l'application racine dans le site Web par défaut).

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

Avant d'activer le nouveau module d'authentification de base, désactivez tous les autres modules d'authentification IIS. Par défaut, seule l'authentification Windows et l'authentification anonyme sont activées. Étant donné que nous ne voulons pas que le navigateur tente de s'authentifier avec les informations d'identification Windows ou qu'il autorise les utilisateurs anonymes, nous désactivons à la fois le module d'authentification Windows et le module d'authentification anonyme.

Activez maintenant le module en l'ajoutant à la liste des modules chargés par notre application. Ouvrez Web.config une fois de plus et ajoutez l'entrée à l'intérieur de la balise <modules>

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

Vous pouvez également déployer le module à l'aide de l'outil d'administration IIS ou de l'outil en ligne de commande APPCMD.EXE.

Le contenu final du fichier Web.config de l'application après ces modifications est fourni dans l'annexe B.

Félicitations, vous avez terminé de configurer le module d'authentification de base personnalisé.

Essayons ! Ouvrez Internet Explorer et effectuez une demande à l'application à l'URL suivante :

http://localhost/

La boîte de dialogue de connexion d'authentification de base doit s'afficher. Saisissez « test » dans le champ « Nom d'utilisateur » et « test » dans le champ « Mot de passe » pour obtenir l'accès. Si vous copiez du code HTML, JPG ou tout autre contenu dans votre application, ils seront également protégés par votre nouveau BasicAuthenticationModule.

Résumé

Dans cet article, vous avez appris à développer et déployer un module managé personnalisé pour une application et à permettre à ce module de fournir des services pour toutes les demandes adressées à l'application.

Vous avez également assisté au développement de composants serveur dans le code managé. Cela permettait de développer un service d'authentification de base découplé du stockage d'informations d'identification Windows.

Si vous êtes aventureux, configurez ce module pour tirer parti de la puissance des services d'application d'appartenance ASP.NET 2.0 pour prendre en charge les magasins d'informations d'identification enfichables. Pour en savoir plus, consultez l'Annexe C.

Recherchez de nombreuses ressources et conseils sur l'écriture de modules IIS dans le blog, http://www.mvolo.com/, ainsi que pour télécharger des modules IIS existants pour vos applications. Pour obtenir des exemples, consultez Rediriger des demandes vers votre application avec le module HttpRedirection, Nice recherche les listes de répertoires pour votre site Web IIS avec DirectoryListingModule et affiche des icônes de fichiers jolies dans vos applications ASP.NET avec IconHandler.

Annexe A : code source du module d'authentification de base

Enregistrez ce code source sous BasicAuthenticationModule.cs dans le répertoire /App_Code pour le déployer rapidement sur votre application.

Remarque

Si vous utilisez le Bloc-notes, veillez à définir Enregistrer sous : tous les fichiers pour éviter d'enregistrer le fichier en tant que 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

    }
}

Annexe B : Web.config pour le module d'authentification de base

Enregistrez cette configuration en tant que fichier Web.config à la racine de votre application :

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

Annexe C : Configuration de l'appartenance

Le service d'appartenance ASP.NET 2.0 permet aux applications d'implémenter rapidement la validation des informations d'identification et la gestion des utilisateurs requises par la plupart des schémas de contrôle d'accès et d'authentification. L'appartenance isole le code de l'application de l'implémentation réelle du magasin d'informations d'identification et fournit plusieurs options d'intégration aux magasins d'informations d'identification existants.

Pour tirer parti de l'appartenance pour cet exemple de module, supprimez les marques de commentaire d'un appel à Membership.ValidateUser à l'intérieur de la méthode ValidateCredentials et configurez un fournisseur d'appartenance pour votre application. Pour en savoir plus sur la configuration de l'appartenance, consultez Configuration d'une application ASP.NET pour utiliser l'appartenance.