Desarrollo de un módulo con .NET
por Mike Volodarsky
Introducción
IIS 7.0 y versiones posteriores permiten extender el servidor mediante módulos que se desarrollan de dos maneras:
- Uso de código administrado y las API de extensibilidad del servidor ASP.NET
- Uso de código nativo y las API de extensibilidad del servidor nativo de IIS
En el pasado, los módulos de ASP.NET tenían una funcionalidad limitada, ya que la canalización de procesamiento de solicitudes ASP.NET era independiente de la canalización de solicitudes del servidor principal.
En IIS, los módulos administrados se vuelven prácticamente tan eficaces como los módulos nativos con la arquitectura de canalización integrada. Y lo que es más importante, los servicios que proporcionan los módulos administrados pueden aplicarse ahora a todas las solicitudes al servidor, no solo a las solicitudes de contenido ASP.NET, como las páginas ASPX. Los módulos administrados se configuran y administran de forma coherente con los módulos nativos y se pueden ejecutar en las mismas fases de procesamiento y ordenaciones que los módulos nativos. Por último, los módulos administrados pueden realizar un conjunto más amplio de operaciones para manipular el procesamiento de solicitudes a través de varias API de ASP.NET agregadas y mejoradas.
En este artículo se muestra cómo extender el servidor con un módulo administrado para agregar la capacidad de realizar la autenticación básica en un almacén de credenciales arbitrario, como la infraestructura de credenciales basada en el proveedor en el sistema de pertenencia ASP.NET 2.0.
Esto permite reemplazar la compatibilidad integrada de autenticación básica en IIS, que está vinculada al almacén de credenciales de Windows, con una que admite almacenes de credenciales arbitrarios o cualquiera de los proveedores de pertenencia existentes enviados con ASP.NET 2.0 como SQL Server, SQL Express o Active Directory.
En este artículo se examinan las siguientes tareas:
- Desarrollo de un módulo administrado mediante las API de ASP.NET
- Implementación de un módulo administrado en el servidor
Para más información sobre los conceptos básicos del desarrollo de módulos y controladores de IIS, consulte Desarrollo de módulos y controladores de IIS7 con .NET Framework.
También puede encontrar muchos recursos y sugerencias sobre cómo escribir módulos de IIS en el blog, http://www.mvolo.com/, así como descargar módulos de IIS existentes para sus aplicaciones. Para obtener algunos ejemplos, consulte Redireccionamiento de solicitudes a la aplicación con el módulo HttpRedirection, Listados de directorios agradables para el sitio web de IIS con DirectoryListingModule y Visualización de iconos de archivos bonitos en las aplicaciones de ASP.NET con IconHandler.
Nota:
El código proporcionado en este artículo se escribe en C#.
Requisitos previos
Para seguir los pasos descritos en este documento, debe tener instaladas las siguientes características de IIS:
ASP.NET
Instale ASP.NET a través del Panel de control de Windows Vista. Seleccione "Programas" - "Activar o desactivar las características de Windows". A continuación, abra "Internet Information Services" - "World Wide Web Services" - "Características de desarrollo de aplicaciones" y active "ASP.NET".
Si tiene una compilación de Windows Server® 2008, abra "Administrador del servidor" - "Roles" y seleccione "Servidor web (IIS)". Haga clic en "Agregar servicios de rol". En "Desarrollo de aplicaciones", active "ASP.NET".
Información general sobre la autenticación básica
La autenticación básica es un esquema de autenticación definido en el protocolo HTTP.1 (RFC 2617). Usa un mecanismo estándar basado en desafíos que funciona de la siguiente manera en un nivel alto:
- El explorador realiza una solicitud a una dirección URL sin credenciales
- Si el servidor requiere autenticación para esa dirección URL, responde con un mensaje de acceso denegado 401 e incluye un encabezado que indica que se admite el esquema de autenticación básico
- El explorador recibe la respuesta y, si está configurada, solicitará al usuario un nombre de usuario o contraseña que incluirá en texto sin formato dentro de un encabezado de solicitud para la siguiente solicitud a la dirección URL
- El servidor recibe el nombre de usuario o la contraseña dentro de un encabezado y los usa para la autenticación
Nota:
Aunque una explicación detallada de este protocolo de autenticación está fuera del ámbito de este artículo, merece la pena mencionar que el esquema de autenticación básico requiere que SSL sea seguro, ya que envía el nombre de usuario y la contraseña en texto sin formato.
IIS incluye compatibilidad con la autenticación básica en las cuentas de Windows almacenadas en el almacén de cuentas local o Active Directory para las cuentas de dominio. Queremos permitir que nuestro usuario se autentique utilizando la autenticación básica, pero que en su lugar valide las credenciales utilizando el servicio de pertenencia ASP.NET 2.0. Esto ofrece la libertad de almacenar información de usuario en una variedad de proveedores de pertenencia existentes, como SQL Server, sin estar vinculado a cuentas de Windows.
Tarea 1: desarrollo de un módulo mediante .NET
En esta tarea, examinaremos el desarrollo de un módulo de autenticación que admite el esquema de autenticación básico HTTP.1. Este módulo se desarrolló con el patrón de módulo estándar de ASP.NET disponible desde ASP.NET v1.0. Este mismo patrón se usa para compilar módulos de ASP.NET que extienden el servidor IIS. De hecho, los módulos de ASP.NET existentes escritos para versiones anteriores de IIS se pueden usar en IIS y aprovechar mejor la integración de ASP.NET para proporcionar más potencia a las aplicaciones web que las usan.
Nota:
El código completo del módulo se proporciona en el Apéndice A.
Un módulo administrado es una clase .NET que implementa la interfaz System.Web.IHttpModule. La función principal de esta clase es registrar uno o varios eventos que se producen en la canalización de procesamiento de solicitudes de IIS y, a continuación, realizar un trabajo útil cuando IIS invoca los controladores de eventos del módulo para esos eventos.
Permite crear un nuevo archivo de código fuente denominado "BasicAuthenticationModule.cs" y crear la clase de módulo (el código fuente completo se proporciona en el Apéndice A):
public class BasicAuthenticationModule : System.Web.IHttpModule
{
void Init(HttpApplication context)
{
}
void Dispose()
{
}
}
La función principal del método Init es conectar los métodos del controlador de eventos del módulo a los eventos de canalización de solicitud adecuados. La clase del módulo proporciona los métodos de identificador de eventos e implementan la funcionalidad deseada proporcionada por el módulo. Esto se describe más detalladamente.
El método Dispose se usa para limpiar cualquier estado del módulo cuando se descarta la instancia del módulo. Normalmente, no se implementa a menos que el módulo use recursos específicos que necesiten liberarse.
Init()
Después de crear la clase, el siguiente paso es implementar el método Init. El único requisito es registrar el módulo para uno o varios eventos de canalización de solicitud. Conecte los métodos de módulo, que siguen la firma del delegado System.EventHandler, a los eventos de canalización deseados expuestos en la instancia de System.Web.HttpApplication proporcionada:
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);
}
El método AuthenticateUser se invoca en cada solicitud durante el evento AuthenticateRequest. Lo utilizamos para autenticar al usuario en función de la información de credenciales presente en la solicitud.
El método IssueAuthenticationChallenge se invoca en cada solicitud durante el evento EndRequest. Es responsable de emitir un desafío de autenticación básico al cliente cada vez que el módulo de autorización rechaza una solicitud y se necesita autenticación.
AuthenticateUser()
Implemente el método AuthenticateUser. Este método hace lo siguiente:
- Extraiga las credenciales básicas si están presentes en los encabezados de solicitud entrantes. Para ver la implementación de este paso, consulte el método de utilidad ExtractBasicAuthenticationCredentials .
- Intenta validar las credenciales proporcionadas a través de la pertenencia (mediante el proveedor de pertenencia predeterminado configurado). Para ver la implementación de este paso, consulte el método de utilidad ValidateCredentials.
- Crea una entidad de seguridad de usuario que identifica al usuario si la autenticación se realiza correctamente y la asocia a la solicitud.
Al final de este procesamiento, si el módulo pudo obtener y validar correctamente las credenciales de usuario, generará una entidad de seguridad de usuario autenticada que otros módulos y código de aplicación usen posteriormente en las decisiones de control de acceso. Por ejemplo, el módulo de autorización de direcciones URL examina al usuario en el siguiente evento de canalización para aplicar las reglas de autorización configuradas por la aplicación.
IssueAuthenticationChallenge()
Implemente el método IssueAuthenticationChallenge. Este método hace lo siguiente:
- Compruebe el código de estado de respuesta para determinar si se rechazó esta solicitud.
- Si es así, emita un encabezado de desafío de autenticación básica en la respuesta para que el cliente se autentique.
Métodos de utilidad
Implemente los métodos de utilidad que usa el módulo, entre los que se incluyen:
- ExtractBasicAuthenticationCredentials. Este método extrae las credenciales de autenticación básicas del encabezado de solicitud de autorización, tal como se especifica en el esquema de autenticación básico.
- ValidateCredentials. Este método intenta validar las credenciales de usuario mediante la pertenencia. La API de pertenencia abstrae el almacén de credenciales subyacente y permite configurar las implementaciones del almacén de credenciales mediante la adición o eliminación de proveedores de pertenencia a través de la configuración.
Nota:
En este ejemplo, se comenta la validación de pertenencia y, en su lugar, el módulo simplemente comprueba si el nombre de usuario y la contraseña son iguales a la cadena "test". Esto se hace para mayor claridad y no está pensado para implementaciones de producción. Se le invita a habilitar la validación de credenciales basadas en pertenencia simplemente anulando los comentarios del código de pertenencia dentro de ValidateCredentials y configurando un proveedor de pertenencia para la aplicación. Consulte el Apéndice C para obtener más información.
Tarea 2: implementación del módulo en la aplicación
Después de crear el módulo en la primera tarea, lo agregaremos a la aplicación.
Implementación en la aplicación
En primer lugar, implemente el módulo en la aplicación. Aquí tiene varias opciones:
Copie el archivo de origen que contiene el módulo en el directorio / /App_Code de la aplicación. Esto no requiere compilar el módulo: ASP.NET compila y carga automáticamente el tipo de módulo cuando se inicia la aplicación. Simplemente guarde este código fuente como BasicAuthenticationModule.cs dentro del directorio /App_Code de la aplicación. Haga esto si no se siente cómodo con los otros pasos.
Compile el módulo en un ensamblado y quite este ensamblado en el directorio /BIN de la aplicación. Esta es la opción más habitual si solo desea que este módulo esté disponible para esta aplicación y no desea enviar el origen del módulo con la aplicación. Compile el archivo de origen del módulo ejecutando lo siguiente desde un símbolo de la línea de comandos:
<PATH_TO_FX_SDK>csc.exe /out:BasicAuthenticationModule.dll /target:library BasicAuthenticationModule.cs
Donde
<PATH_TO_FX_SDK>
es la ruta de acceso al SDK de .NET Framework que contiene el compilador de CSC.EXE.Compile el módulo en un ensamblado con nombre seguro y registre este ensamblado en la GAC. Esta es una buena opción si desea que varias aplicaciones de la máquina usen este módulo. Para más información sobre cómo crear ensamblados con nombre seguro, consulte Creación y uso de ensamblados con nombre seguro.
Antes de realizar cambios de configuración en el archivo web.config de la aplicación, debemos desbloquear algunas de las secciones de configuración bloqueadas en el nivel de servidor de forma predeterminada. Ejecute lo siguiente desde un símbolo del sistema con privilegios elevados (inicie > haciendo clic con el botón derecho en Cmd.exe y elija "Ejecutar como administrador"):
%windir%\system32\inetsrv\APPCMD.EXE unlock config /section:windowsAuthentication
%windir%\system32\inetsrv\APPCMD.EXE unlock config /section:anonymousAuthentication
Después de ejecutar estos comandos, podrá definir estas secciones de configuración en el archivo web.config de la aplicación.
Configure el módulo para que se ejecute en la aplicación. Empiece por crear un nuevo archivo web.config, que contendrá la configuración necesaria para habilitar y usar el nuevo módulo. Para empezar, agregue el siguiente texto y guárdelo en la raíz de la aplicación (%systemdrive%\inetpub\wwwroot\web.config
si usa la aplicación raíz en el sitio web predeterminado).
<configuration>
<system.webServer>
<modules>
</modules>
<security>
<authentication>
<windowsAuthentication enabled="false"/>
<anonymousAuthentication enabled="false"/>
</authentication>
</security>
</system.webServer>
</configuration>
Antes de habilitar el nuevo módulo de autenticación básica, deshabilite todos los demás módulos de autenticación de IIS. De forma predeterminada, solo se habilita la autenticación de Windows y la autenticación anónima. Dado que no queremos que el explorador intente autenticarse con sus credenciales de Windows o permitir usuarios anónimos, deshabilitamos tanto el módulo autenticación de Windows como el módulo de autenticación anónima.
Ahora habilite el módulo agregándolo a la lista de módulos cargados por nuestra aplicación. Abra web.config una vez más y agregue la entrada dentro de la etiqueta <modules>
<add name="MyBasicAuthenticationModule" type="IIS7Demos.BasicAuthenticationModule" />
También puede implementar el módulo mediante la herramienta de administración de IIS o la herramienta de línea de comandos APPCMD.EXE.
El contenido final del archivo web.config de la aplicación después de estos cambios se proporciona en el Apéndice B.
Enhorabuena, ha terminado de configurar el módulo de autenticación básica personalizado.
Probémoslo. Abra Internet Explorer y realice una solicitud a la aplicación en la siguiente dirección URL:
http://localhost/
Debería ver el cuadro de diálogo de inicio de sesión de autenticación básico. Escriba "test" en el campo "Nombre de usuario:" y "test" en el campo "Contraseña:" para obtener acceso. Tenga en cuenta que si copia HTML, JPG o cualquier otro contenido en la aplicación, también estará protegido por el nuevo BasicAuthenticationModule.
Resumen
En este artículo, ha aprendido a desarrollar e implementar un módulo administrado personalizado para una aplicación y a habilitar ese módulo para proporcionar servicios para todas las solicitudes a la aplicación.
También ha sido testigo de la eficacia de desarrollar componentes de servidor en código administrado. Esto ha permitido desarrollar un servicio de autenticación básico que se desacopla del almacenamiento de credenciales de Windows.
Si es aventurero, configure este módulo para aprovechar la eficacia de los servicios de aplicación de pertenencia de ASP.NET 2.0 para admitir almacenes de credenciales conectables. Consulte el Apéndice C para obtener más información.
Busque muchos recursos y sugerencias sobre cómo escribir módulos de IIS en el blog, http://www.mvolo.com/, así como descargar módulos IIS existentes para sus aplicaciones. Para obtener algunos ejemplos, consulte Redireccionamiento de solicitudes a la aplicación con el módulo HttpRedirection, Listados de directorios agradables para el sitio web de IIS con DirectoryListingModule y Visualización de iconos de archivos bonitos en las aplicaciones de ASP.NET con IconHandler.
Apéndice A: código fuente del módulo de autenticación básica
Guarde este código fuente como BasicAuthenticationModule.cs dentro del directorio /App_Code para implementarlo rápidamente en la aplicación.
Nota:
Si usa el Bloc de notas, asegúrese de establecer Guardar como: todos los archivos para evitar guardar el archivo 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 el módulo de autenticación básica
Guarde esta configuración como archivo web.config en la raíz de la aplicación:
<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: configuración de la pertenencia
El servicio de pertenencia ASP.NET 2.0 permite a las aplicaciones implementar rápidamente la validación de credenciales y la administración de usuarios requeridas por la mayoría de los esquemas de autenticación y control de acceso. La pertenencia aísla el código de aplicación de la implementación real del almacén de credenciales y proporciona una serie de opciones para la integración con almacenes de credenciales existentes.
Para aprovechar las ventajas de la pertenencia a este ejemplo de módulo, quite la marca de comentario de una llamada a Membership.ValidateUser dentro del método ValidateCredentials y configure un proveedor de pertenencia para la aplicación. Para obtener más información sobre cómo configurar la pertenencia, consulte Configuración de una aplicación de ASP.NET para usar la pertenencia.