Поделиться через


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

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

Введение

СЛУЖБЫ IIS 7.0 и более поздних версий позволяют расширять сервер с помощью модулей, разработанных двумя способами:

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

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

В 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 Framework.

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

Примечание

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

Предварительные требования

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

ASP.NET

Установите ASP.NET с помощью панель управления Windows Vista. Выберите "Программы" — "Включить или отключить компоненты Windows". Затем откройте "Службы IIS" — "Веб-службы" - "Функции разработки приложений" и проверка "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, чтобы обеспечить больше возможностей для веб-приложений, которые их используют.

Примечание

Полный код модуля приведен в приложении А.

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

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

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. Этот метод извлекает учетные данные обычной проверки подлинности из заголовка запроса авторизации, как указано в базовой схеме проверки подлинности.
  • ValidateCredentials. Этот метод пытается проверить учетные данные пользователя с помощью членства. 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.

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

Перед внесением изменений конфигурации в файл 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 приложения после этих изменений приведено в приложении Б.

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

Попробуем сделать это! Откройте интернет-Обозреватель и отправьте запрос к приложению по следующему 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.

Приложение А. Исходный код базового модуля проверки подлинности

Сохраните этот исходный код как 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

    }
}

Приложение Б. 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>

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

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

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