Разработка модуля с помощью .NET

Майк Володарский

Введение

IIS 7.0 и более поздних версий позволяет расширить сервер по модулям, разработанным двумя способами:

  • Использование управляемого кода и API расширения сервера ASP.NET
  • Использование машинного кода и API расширяемости собственного сервера IIS

В прошлом модули ASP.NET были ограничены в функциональных возможностях, так как конвейер обработки запросов ASP.NET отделен от конвейера запросов основного сервера.

В IIS управляемые модули становятся практически столь же мощными, как встроенные модули благодаря интегрированной архитектуре конвейера. Самое важное— службы, предоставляемые управляемыми модулями, теперь можно применять ко всем запросам к серверу, а не только к запросам на ASP.NET содержимое, например страницы ASPX. Управляемые модули настраиваются и управляются согласованно с собственными модулями и могут выполняться на одинаковых этапах обработки и упорядочиваниях, что и собственные модули. Наконец, управляемые модули могут выполнять более широкий набор операций для управления обработкой запросов с помощью нескольких добавленных и расширенных API-интерфейсов ASP.NET.

В этой статье показано расширение сервера с помощью управляемого модуля, чтобы добавить возможность выполнять обычную проверку подлинности в произвольном хранилище учетных данных, например инфраструктуру учетных данных на основе поставщика в системе членства ASP.NET 2.0.

Это позволяет заменить встроенную базовую поддержку проверки подлинности в службах IIS, которая привязана к хранилищу учетных данных Windows, на поддержку произвольных хранилищ учетных данных или любого из существующих поставщиков учетных данных, входящих в поставляемый комплект ASP.NET 2.0, таких как SQL Server, SQL Express или Active Directory.

В этой статье рассматриваются следующие задачи:

  • Разработка управляемого модуля с помощью API ASP.NET
  • Развертывание управляемого модуля на сервере

Дополнительные сведения о разработке модулей и обработчиков IIS см. в статье "Разработка модулей и обработчиков IIS7 с помощью платформы .NET".

Вы также можете найти множество ресурсов и советов по написанию модулей IIS в блоге, http://www.mvolo.com/а также скачать существующие модули IIS для приложений. В некоторых примерах см. примеры перенаправления запросов к приложению с помощью модуля HttpRedirection, списки каталогов для веб-сайта IIS с помощью DirectoryListingModule и отображение красивых значков файлов в приложениях ASP.NET с помощью IconHandler.

Замечание

Код, указанный в этой статье, написан в C#.

Необходимые условия

Чтобы выполнить действия, описанные в этом документе, необходимо установить следующие компоненты IIS:

ASP.NET

Установите ASP.NET с помощью панели управления Windows Vista. Выберите "Программы" — "Включить или отключить функции Windows". Затем откройте "Интернет-информационные службы" — "World Wide Web Services" — "Функции разработки приложений" и проверьте "ASP.NET".

Если у вас есть сборка Windows Server® 2008, откройте "Диспетчер серверов" — "Роли" и выберите "Веб-сервер (IIS)". Нажмите кнопку "Добавить службы ролей". В разделе "Разработка приложений" установите флажок "ASP.NET".

Общие сведения о базовой проверке подлинности

Базовая проверка подлинности — это схема проверки подлинности, определенная в протоколе HTTP.1 (RFC 2617). Он использует стандартный механизм на основе вызовов, который работает следующим образом в общей схеме:

  • Браузер запрашивает URL-адрес без учетных данных
  • Если серверу требуется проверка подлинности для этого URL-адреса, он отвечает с сообщением об отказе в доступе 401 и содержит заголовок, указывающий, что базовая схема проверки подлинности поддерживается.
  • Браузер получает ответ и, если он настроен, запросит у пользователя имя пользователя и пароль. Их будет включать в обычный текст в заголовке следующего запроса к URL-адресу.
  • Сервер получает имя пользователя и пароль внутри заголовка и использует их для проверки подлинности.

Замечание

Хотя подробное обсуждение этого протокола проверки подлинности выходит за рамки этой статьи, стоит отметить, что базовая схема проверки подлинности требует защиты SSL, так как она отправляет имя пользователя или пароль в виде обычного текста.

IIS поддерживает базовую проверку подлинности для учетных записей Windows, хранящихся в локальном хранилище учетных записей или в Active Directory для учетных записей домена. Мы хотим предоставить возможность нашему пользователю проходить аутентификацию с помощью базовой проверки, но проверка учетных данных будет осуществляться с помощью службы Членства ASP.NET 2.0. Это дает возможность хранить сведения о пользователе в различных существующих поставщиках членства, таких как SQL Server, без привязки к учетным записям Windows.

Задача 1. Разработка модуля с помощью .NET

В этой задаче мы рассмотрим разработку модуля проверки подлинности, поддерживающего базовую схему проверки подлинности HTTP.1. Этот модуль был разработан с помощью стандартного шаблона модуля ASP.NET с ASP.NET версии 1.0. Этот же шаблон используется для создания модулей ASP.NET, расширяющих сервер IIS. На самом деле существующие модули ASP.NET, написанные для предыдущих версий IIS, можно использовать на платформах IIS и воспользоваться преимуществами улучшенной интеграции ASP.NET, чтобы предоставить больше возможностей для веб-приложений, которые используют их.

Замечание

Полный код модуля представлен в приложении A.

Управляемый модуль — это класс .NET, реализующий интерфейс System.Web.IHttpModule . Основная функция этого класса заключается в регистрации для одного или нескольких событий, происходящих в конвейере обработки запросов IIS, а затем выполнить некоторую полезную работу при вызове обработчиков событий модуля для этих событий.

Позволяет создать исходный файл с именем "BasicAuthenticationModule.cs" и создать класс модуля (полный исходный код предоставляется в приложении A):

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

Основная функция метода Init заключается в перенаправке методов обработчика событий модуля в соответствующие события конвейера запроса. Класс модуля предоставляет методы обработки событий и реализует необходимые функциональные возможности, предоставляемые модулем. Это подробно рассматривается.

Метод Dispose используется для очистки любого состояния модуля при удалении экземпляра модуля. Обычно он не реализуется, если модуль не использует определенные ресурсы, которые требуют освобождения.

Init()

После создания класса следующим шагом является реализация метода Init . Единственным требованием является регистрация модуля для одного или нескольких событий конвейера запросов. Свяжите методы модуля, следующие сигнатуре делегата System.EventHandler, с нужными событиями обработки, представленными в экземпляре System.Web.HttpApplication.

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

Метод AuthenticateUser вызывается при каждом запросе во время события AuthenticateRequest . Мы используем его для проверки подлинности пользователя на основе учетных данных, присутствующих в запросе.

Метод IssueAuthenticationChallenge вызывается при каждом запросе во время события EndRequest . Он отвечает за выдачу базовой проверки подлинности обратно клиенту всякий раз, когда модуль авторизации отклоняет запрос и требуется проверка подлинности.

AuthenticateUser()

Реализуйте метод AuthenticateUser . Этот метод выполняет следующие действия:

  • Извлеките основные учетные данные, если они присутствуют из заголовков входящих запросов. Сведения о реализации этого шага см. в методе служебной программы ExtractBasicAuthenticationCredentials .
  • Пытается проверки предоставленных учетных данных через членство (используя настроенного поставщика членства по умолчанию). Сведения о реализации этого шага см. в методе служебной программы ValidateCredentials .
  • Создает учетную запись пользователя, идентифицирующую его в случае успешной проверки подлинности, и связывает её с запросом.

По завершении этой обработки, если модулю удалось получить и проверить учетные данные пользователя, он создаст аутентифицированный субъект пользователя, который будет использоваться другими модулями и кодом приложения в решениях по управлению доступом. Например, модуль авторизации URL-адресов проверяет пользователя в следующем событии конвейера, чтобы применить правила авторизации, настроенные приложением.

IssueAuthenticationChallenge()

Реализуйте метод IssueAuthenticationChallenge . Этот метод выполняет следующие действия:

  • Проверьте код состояния ответа, чтобы определить, был ли отклонен этот запрос.
  • Если это так, в ответе выдайте заголовок запроса базовой аутентификации, чтобы инициировать процесс аутентификации клиента.

Утилитарные методы

Реализуйте служебные методы, используемые модулем, в том числе:

  • ExtractBasicAuthenticationCredentials. Этот метод извлекает учетные данные для базовой аутентификации из заголовка запроса Authorize, как предусмотрено в схеме базовой аутентификации.
  • ValidateCredentials. Этот метод пытается проверить учетные данные пользователя посредством Membership. API членства абстрагирует базовое хранилище учетных данных и позволяет настроить реализации хранилища учетных данных, добавив или удалив поставщиков членства с помощью конфигурации.

Замечание

В этом примере проверка членства отключена, и модуль просто проверяет, равны ли имя пользователя и пароль строке "test". Это делается для ясности и не предназначено для рабочих развертываний. Вам предлагается включить проверку учетных данных на основе членства, просто раскомментировав код членства в ValidateCredentials и настроив поставщика членства для вашего приложения. Дополнительные сведения см. в приложении C.

Задача 2. Развертывание модуля в приложении

После создания модуля в первой задаче мы добавим его в приложение.

Развертывание в приложении

Сначала разверните модуль в приложении. Здесь есть несколько вариантов:

  • Скопируйте исходный файл, содержащий модуль, в каталог /App_Code приложения. Для этого не требуется компиляция модуля— ASP.NET автоматически компилирует и загружает тип модуля при запуске приложения. Просто сохраните этот исходный код как BasicAuthenticationModule.cs в каталоге /App_Code приложения. Сделайте это, если вы не чувствуете себя комфортно с другими шагами.

  • Скомпилируйте модуль в сборку и удалите эту сборку в каталоге /BIN приложения. Это наиболее типичный вариант, если вы хотите, чтобы этот модуль был доступен для этого приложения, и вы не хотите отправлять источник модуля с приложением. Скомпилируйте исходный файл модуля, выполнив следующую команду из командной строки:

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

    Где <PATH_TO_FX_SDK> находится путь к пакету SDK для .NET Framework, который содержит компилятор CSC.EXE.

  • Скомпилируйте модуль в строго именованную сборку и зарегистрируйте эту сборку в GAC. Это хороший вариант, если вы хотите, чтобы несколько приложений на компьютере использовали этот модуль. Дополнительные сведения о создании строго именованных сборок см. в статье "Создание и использование сборок с строгим именем".

Прежде чем вносить изменения конфигурации в файл web.config приложения, необходимо разблокировать некоторые разделы конфигурации, заблокированные на уровне сервера по умолчанию. Выполните следующую команду из командной строки с повышенными привилегиями (щелкните > правой кнопкой мыши Cmd.exe и выберите "Запуск от имени администратора"):

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

После выполнения этих команд вы сможете определить эти разделы конфигурации в файле web.config приложения.

Настройте модуль для запуска в приложении. Начните с создания нового файла web.config, который будет содержать конфигурацию, необходимую для включения и использования нового модуля. Сначала добавьте приведенный ниже текст и сохраните его в корне приложения (%systemdrive%\inetpub\wwwroot\web.config если используется корневое приложение на веб-сайте по умолчанию).

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

Прежде чем включить новый базовый модуль проверки подлинности, отключите все остальные модули проверки подлинности IIS. По умолчанию включена только проверка подлинности Windows и анонимная проверка подлинности. Так как мы не хотим, чтобы браузер попытается выполнить проверку подлинности с помощью учетных данных Windows или разрешить анонимным пользователям, мы отключаем модуль проверки подлинности Windows и модуль анонимной проверки подлинности.

Теперь включите модуль, добавив его в список модулей, загруженных нашим приложением. Откройте web.config еще раз и добавьте запись внутри тега <modules>

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

Вы также можете развернуть модуль с помощью средства администрирования IIS или средства командной строки APPCMD.EXE.

Окончательное содержимое файла web.config приложения после этих изменений приведено в приложении B.

Поздравляем, вы закончили настройку пользовательского базового модуля проверки подлинности.

Давайте попробуем! Откройте Internet Explorer и выполните запрос к приложению по следующему URL-адресу:

http://localhost/

Вы увидите диалоговое окно входа в систему для проверки подлинности в базовом режиме. Введите "test" в поле "Имя пользователя:" и "test" в поле "Пароль:", чтобы получить доступ. Обратите внимание, что при копировании HTML, JPG или любого другого содержимого в приложение они также будут защищены новым приложением BasicAuthenticationModule.

Сводка

Из этой статьи вы узнали, как разрабатывать и развертывать настраиваемый управляемый модуль для приложения и предоставлять службы для всех запросов к приложению.

Вы также стали свидетелем возможностей разработки компонентов сервера на управляемом коде. Это позволило разработать базовую службу проверки подлинности, которая отделяется от хранилища учетных данных Windows.

Если вы предприимчивы, настройте этот модуль для использования возможностей служб приложений членства ASP.NET 2.0 для поддержки подключаемых хранилищ учетных данных. Дополнительные сведения см. в приложении C.

Найдите множество ресурсов и советов по написанию модулей IIS в блоге, http://www.mvolo.com/а также скачайте существующие модули IIS для приложений. В некоторых примерах см. примеры перенаправления запросов к приложению с помощью модуля HttpRedirection, списки каталогов для веб-сайта IIS с помощью DirectoryListingModule и отображение красивых значков файлов в приложениях ASP.NET с помощью IconHandler.

Приложение A. Базовый исходный код модуля проверки подлинности

Сохраните этот исходный код как BasicAuthenticationModule.cs в каталоге /App_Code, чтобы быстро развернуть его в приложении.

Замечание

Если вы используете Блокнот, установите флажок "Сохранить как": все файлы, чтобы избежать сохранения файла как 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

    }
}

Приложение B: Web.config для модуля базовой проверки подлинности

Сохраните эту конфигурацию как файл web.config в корневом каталоге приложения:

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

Приложение C. Настройка членства

Служба членства ASP.NET 2.0 позволяет приложениям быстро реализовать проверку учетных данных и управление пользователями, необходимые для большинства схем проверки подлинности и управления доступом. Членство изолирует код приложения от фактической реализации хранилища учетных данных и предоставляет ряд вариантов интеграции с существующими хранилищами учетных данных.

Чтобы воспользоваться преимуществами членства в этом примере модуля, раскомментируйте вызов Membership.ValidateUser в методе ValidateCredentials и настройте поставщика членства для приложения. Дополнительные сведения о настройке членства см. в разделе "Настройка приложения ASP.NET для использования членства".