Практикум прикладного программирования на C# в среде VS.NET 2005

Безопасность: аутентификация с помощью форм

Разбить на страницы
Показывать лекцию целиком

Файлы к лабораторной работе Вы можете скачать здесь.

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

Основные задачи по защите сайта

Основные задачи по защите сайта следующие:

  • Аутентификация - опознавание источника сообщений по схеме "свой-чужой" с целью установить, является ли пользователь зарегистрированным на сайте или ананимным. Примером может служить проверка пропуска при входе в организацию
  • Авторизация - определение уровня полномочий по допуску к важной информации и выполнению над ней определенных действий. Примером может быть допуск к определенным производствам или документации на предприятии
  • Конфединциальность - изоляция данных одного пользователя от любого вида доступа со стороны других пользователей. Обеспечивается с помощью шифрования данных в каналах связи и в местах постоянного или временного хранения
  • Целостность - защита данных от изменений в результате неавторизованного вмешательства
  • Аутентификация нового пользователя начинается с его регистрации. Пользователю предлагается страница регистрации с текстовыми полями, через которую у него запрашиваются некоторые идентификационные данные. По этим данным создается и сохраняется учетная запись, которая для узнаваемости пользователя на сайте является некоторым маркером или удостоверением. Зарегистрировавшемуся пользователю может быть отослан некоторый ключ, который в дальнейшем будет служить дополнительным условием при доступе к сайту.

    Теперь при запросе ограниченного ресурса пользователь предъявит свое удостоверение, которое система верифицирует и примет решение: если удостоверение пользователя подтверждается, ему предоставляется доступ к Web-ресурсу, иначе будет предложена регистрация. Процесс аутентификации может выглядеть примерно так

    В приложениях ASP.NET аутентификация может быть реализована одним из следующих способов:

  • Аутентификация с помощью форм - поддерживается классом System.Web.Security.FormsAuthenticationModule
  • Аутентификация Windows - поддерживается классом System.Web.Security.WindowsAuthenticationModule
  • Аутентификация с помощью паспортов - поддерживается классом System.Web.Security.PassportAuthenticationModule
  • Специализированная аутентификация - поддерживается внебиблиотечными классами сторонних производителей или самодельным классом
  • Системы коммуникации шифруются с помощью системы SSL (Secure Sockets Layer - уровень защищенных сокетов). Поскольку система SSL является службой IIS (Internet Information Services - службы информационной поддержки Internet) и работает под HTTP, то специально коммуникации нам шифровать не нужно - все пересылки шифрует сама служба. Единственное отличие - адрес запроса, защищенный SSL, начинается не с http://, а с https://. И трафик SSL проходит не через порт 80 для обычных запросов, а через порт 443.

    Сертификаты

    Обычно сервер, которому клиент собирается пересылать важные данные, маркируется сертификатом. Сертификат - это некоторый паспорт сервера, подтверждающий тот факт, что ему можно доверять. Организация, которой принадлежит сервер, приобретает сертификат у известного центра сертификации CA (Certificate Authority) и инсталирует его на своем Web-сервере.

    Наиболее популярными центрами сертификации являются следующие сайты:

  • Thawte - http://www.thawte.com
  • VeriSign - http://www.verisign.com
  • Инсталированный на сервере сертификат содержит следующую информацию:

  • Серийный номер сертификата
  • Имя, название организации и адрес держателя сертификата
  • Даты проверки сертификата
  • Открытый ключ, который будет рассылаться клиентам для шифрования симметричного ключа сеанса
  • Клиент (броузер) доверяет CA и поэтому готов доверять подписанной CA информации. В свою очередь, CA сохраняет информацию о каждом зарегистрированном пользователе. Начало работы по защищенному каналу связи начинается так:

  • Броузер посылает запрос на соединение с сервером
  • В ответ сервер присылает свой сертификат
  • Броузер запрашивает из центра сертификации оригинал поступившего сертификата и сравнивает его с присланным сервером
  • Если информация в сертификатах совпадает, броузер считает адресуемый сервер заслуживающим доверия и решается на соединение. Он генерирует случайным образом симметричный ключ шифрования подходящей для него длины, шифрует его присланным в сертификате открытым ключем и отсылает серверу
  • Сервер получает от броузера зашифрованный ключ сеанса и расшифровывает его своим закрытым ключем. После этого у сервера и броузера есть одинаковый секретный ключ, с помощью которого они готовы применять симметричное шифрование к пересылаемой друг другу информации на протяжении всего текущего сеанса
  • Аутентификация форм

    Аутентификация (распознавание) на основе форм реализуется в пространстве имен System.Web.Security классами:

  • FormsAuthenticationModule
  • FormsAuthentication
  • FormsAuthenticationEventArgs
  • FormsAuthenticationTicket
  • FormsIdentity
  • Выбор этого режима устанавливается только в одном единственном месте - конфигурационном файле web.config корневого каталога сайта с помощью элемента

    <authentication mode="Forms" />

    Аутентификацию форм можно выполнять и с помощью API-интерфейсов Membership и Roles (об этом позже).

    Аутентификация форм основана на удостоверениях (мандатах), в которых хранится информация о зарегистрированном пользователе. Мандаты хранятся на сервере в базе данных зашифрованными и пересылаются при каждом запросе в cookie-наборах. ASP.NET при каждом запросе обрабатывает cookie-набор и задает по нему уровень безопасности запроса.

    Для реализации аутентификации форм нужно выполнить следующее:

  • Настроить режим аутентификации форм в файле web.config
  • Настроить службу IIS для разрешения анонимного доступа к виртуальному каталогу
  • Настроить ASP.NET для ограничения доступа к защищенным каталогам
  • Создать страницу регистрации
  • Настройка режима аутентификации форм

    В файле web.config виртуального корневого каталога сайта необходимо определить нужные параметры аутентификации, как свойства класса System.Web.Configuration.FormsAuthenticationConfiguration, из следующего набора:

    Опции аутентификации форм в файле web.config
    Опция Значение по умолчанию Описание
    Name .ASPXAUTH Имя cookie-набора HTTP для использования в аутентификации. При работе на одном сервере нескольких приложений cookie-набору безопасности каждого приложения нужно задать уникальное имя для гарантированной их изоляции
    LoginUrl login.aspx Определяет страницу регистрации пользователя при запросе защищенной страницы (login - вход в систему)
    Timeout 30 Время жизни cookie-набора безопасности в минутах. Если установлен режим скользящего устаревания и период между двумя соседними запросами не превысит этого таймаута, то ASP.NET обновит метку времени в cookie-наборе. Но если простой превысит установленный таймаут, будет затребована новая регистрация пользователя
    SlidingExpiration false Включает режим скользящего устаревания cookie-набора безопасности. В этом режиме время устаревания при каждом запросе будет сбрасываться, если очередной простой не превысит указанное значение Timeout
    Cookieless UseDeviceProfile Указывает способ пересылки удостоверения пользователя
    Protection All

    Задает уровень безопасности cookie-наборов аутентификации:

  • None - не шифровать и не подписывать
  • Encryption - только шифровать
  • Validation - только подписывать
  • All - шифровать и подписывать
  • RequireSSL false При включении требует передачи через SSL. Если SSL не активирован на сервере, то аутентификация форм работать не будет
    EnableCrossAppRedirects false Разрешает переадресацию между приложениями при использовании аутентификации форм. Оба приложения должны опираться на одно и то же хранилище удостоверений
    DefaultUrl default.aspx Указывает страницу, на которую направляется пользователь после успешной регистрации, если он напрямую запросил страницу регистрации, указанную в LoginUrl
    Domain пусто Может указывать хост, в рамках которого любое приложение может использовать одни и те же cookie-наборы
    Path / Путь для cookie-наборов. Рекомендуется по умолчанию

    Перечисленные свойства устанавливаются как атрибуты дочернего дескриптора <forms> секции <authentication mode= "Forms" />. Вот пример использования полного набора этих атрибутов в файле web.config

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName"
                    loginUrl="MyLogin.aspx"
                    timeout="20"
                    slidingExpiration="true"
                    cookieless="AutoDetect"
                    protection="All"
                    requireSSL="false"
                    enableCrossAppRedirects="false"
                    defaultUrl="MyDefault.aspx"
                    domain=""
                    path="/"
                /> 
            </authentication>
        </system.web>
    </configuration>

    Хранение удостоверений в конфигурационном файле

    Хоть это и не самый лучший способ хранения удостоверений пользователей, зато самый простой. Вообще, удостоверения можно хранить где угодно, только нужно предусмотреть соответствующий код их сравнения с введенными на странице регистрации данными. Если удостоверения хранятся в файле web.config, то их нужно помещать в дочерний для <forms> дескриптор <credentials>. Вот пример для трех пользователей

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName"
                    loginUrl="MyLogin.aspx"
                    timeout="20"
                    slidingExpiration="true"
                    cookieless="AutoDetect"
                    protection="All"
                    requireSSL="false"
                    enableCrossAppRedirects="false"
                    defaultUrl="MyDefault.aspx"
                    domain=""
                    path="/"
                >
                    <credentials passwordFormat="Clear">
                        <user name="Admin" password="Root" />
                        <user name="Petja" password="Xa-xa" />
                        <user name="Vasja" password="Xo-xo" />
                    </credentials>
                </forms>
            </authentication>
        </system.web>
    </configuration>

    Упражнение 1. Управление доступом анонимных пользователей

    Рассмотрим пример ограничения доступа к страницам сайта с помощью аутентификации форм и хранением удостоверений в конфигурационном файле. Пользователей в конфигурационный файл можно добавлять как декларативно, непосредственно редактируя секцию <credentials>, так и программно.

  • Создайте пустой проект Web-приложения командой File/New/Web Site с именем Login
  • Добавьте к приложению корневой конфигурационный файл Web.config, выполнив команду Website/Add New Item
  • Заполните конфигурационный файл кодом настройки аутентификации форм и декларативным созданием пользователей
  • <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName" 
                    loginUrl="MyLogin.aspx" 
                    defaultUrl="MyDefault.aspx"
                    protection="All" 
                    timeout="20" 
                    path="/" 
                    requireSSL="false"
                    slidingExpiration="true" 
                    cookieless="AutoDetect" 
                    domain=""
                    enableCrossAppRedirects="false">
                    <credentials passwordFormat="Clear">
                        <user name="Admin" password="Root" />
                        <user name="Петя" password="Уфamp;Уф" />
                        <user name="Вася" password='!$-*?"' />
                    </credentials>
                </forms>
            </authentication>
        </system.web>
    </configuration>

    Значение атрибута passwordFormat="Clear" обеспечивает открытое хранение пользователей в конфигурационном файле без всякого шифрования. Этот параметр применяется в надежде на то, что система автоматически ограничивает доступ конечных пользователей к конфигурационным файлам и что эти секретные данные никто не увидит. Но администратор сайта имеет доступ к конфигурационным файлам сайта, да и продвинутые злоумышленники тоже могут добраться до этой информации, обойдя защиту системы. Поэтому существуют встроенные алгоритмы хеширования ( MD5, SHA1 ), использование которых мы рассмотрим чуть позже.

    Программное добавление пользователей в конфигурационный файл

  • Добавьте к проекту командой Website/Add New Item страницу без отделенного кода с именем LoginAddUsers.aspx по следующему шаблону
  • Заполните страницу LoginAddUsers.aspx следующим кодом
  • <%@ Page Language="C#" %>
    <%@ Import Namespace="System.Web.Configuration" %>
        
    <script runat="server">
        
        protected void btnAddUsers_Click(object sender, EventArgs e)
        {
            // Учетные записи для новых аутентифицированных пользователей
            string[,] authUsers = {
                                    {"user1", "password1"},
                                    {"user2", "password2"},
                                    {"user3", "password3"}
                                  };
            
            // Загрузить содержимое корневого Web.config
            Configuration myConfig = System.Web.Configuration.
                WebConfigurationManager.OpenWebConfiguration("~/");
            
            // Найти содержимое секции system.web
            ConfigurationSectionGroup systemWeb = myConfig.SectionGroups["system.web"];
            
            // Найти содержимое секции authentication 
            System.Web.Configuration.AuthenticationSection authSec =
                (System.Web.Configuration.AuthenticationSection)
                systemWeb.Sections["authentication"];
            
            // Установить параметр passwordFormat (Clear, MD5, SHA1) 
            authSec.Forms.Credentials.PasswordFormat = System.Web.Configuration.FormsAuthPasswordFormat.Clear;
            
            // Добавить новых пользователей
            for (int i = 0; i < authUsers.Length / 2; i++)
                authSec.Forms.Credentials.Users.Add(new System.Web.Configuration.FormsAuthenticationUser(
                    authUsers[i, 0], authUsers[i, 1]));
            
            // Обновить файл Web.config 
            myConfig.Save();
        
            // Отсылаем сообщение 
            lblUsersResult.Text = "Новые пользователи добавлены!!!";
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div style="text-align: center">
        <h1>Административная страница добавления учетных записей в Web.config</h1>
            <p>
                <asp:Button ID="btnAddUsers" runat="server" Text="Добавить" OnClick="btnAddUsers_Click" /></p>
            <p>
                <asp:Label ID="lblUsersResult" runat="server"></asp:Label></p>
        </div>
        </form>
    </body>
    </html>

    Обратите внимание! Чтобы не использовать полные имена классов вместе с их пространствами имен:

    В страницах с отделенным кодом используется инструкция using

    В страницах с совмещенным кодом используется директива

    <%@ Import Namespace="System.Web.Configuration" %>

  • В режиме редактирования страницы LoginAddUsers.aspx выполните команду File/View in Browser
  • Откройте файл Web.config и убедитесь, что страница LoginAddUsers.aspx при выполнении программно добавила новых пользователей, а также скорректировала имена старых пользователей путем приведения символов к нижнему регистру. Но это несущественно, поскольку ASP.NET при сравнении имен регистр символов не учитывает
  • <credentials passwordFormat="Clear">
         <user name="admin" password="Root" />
         <user name="петя" password="Уфamp;Уф" />
         <user name="вася" password="!$-*?quot;" />
         <user name="user1" password="password1" />
         <user name="user2" password="password2" />
         <user name="user3" password="password3" />
        </credentials>
  • Добавьте к проекту две страницы без отделенного кода с именами Default.aspx и MyDefault.aspx со следующим кодом
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void SignOutAction_Click(object sender, EventArgs e)
        {
            // Уничтожить cookie-набор 
            FormsAuthentication.SignOut();
            // Направить на страницу регистрации
            //FormsAuthentication.RedirectToLoginPage();
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <h1>
                Страница с ограниченным доступом Default.aspx</h1>
            <p>
                <asp:Button ID="SignOutAction" runat="server" 
                Text="Отменить регистрацию" OnClick="SignOutAction_Click" />
                <asp:HyperLink ID="HyperLink1" runat="server" 
                NavigateUrl="~/MyDefault.aspx">На страницу MyDefault.aspx...
                </asp:HyperLink></p>
        </div>
        </form>
    </body>
    </html>
    <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void SignOutAction_Click(object sender, EventArgs e)
        {
            // Уничтожить cookie-набор 
            FormsAuthentication.SignOut();
            // Направить на страницу регистрации
            //FormsAuthentication.RedirectToLoginPage();
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <h1>
                    Страница с ограниченным доступом MyDefault.aspx</h1>
                <p>
                    <asp:Button ID="SignOutAction" runat="server" 
                    OnClick="SignOutAction_Click" Text="Отменить регистрацию" />
                    <asp:HyperLink ID="HyperLink1" runat="server" 
                    NavigateUrl="~/Default.aspx">На страницу Default.aspx...
                    </asp:HyperLink></p>
            </div>
        </form>
    </body>
    </html>
  • В проводнике решений вызовите контекстное меню для страницы Default.aspx или MyDefault.aspx и выполните команду View in Browser. Убедитесь, что пока ASP.NET разрешает пользоваться этими страницами без регистрации, т.е. любым анонимным пользователям
  • Добавьте в конфигурационный файл блок авторизации, запрещающий доступ всем анонимным пользователям и требующий обязательной регистрации
  • Окончательный вид файла Web.config должен стать таким

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName" 
                    loginUrl="MyLogin.aspx" 
                    defaultUrl="MyDefault.aspx"
                    protection="All" 
                    timeout="20" 
                    path="/" 
                    requireSSL="false"
                    slidingExpiration="true" 
                    cookieless="AutoDetect" 
                    domain=""
                    enableCrossAppRedirects="false">
                    <credentials passwordFormat="Clear">
                        <user name="admin" password="Root" />
                        <user name="петя" password="Уфamp;Уф" />
                        <user name="вася" password="!$-*?quot;" />
                        <user name="user1" password="password1" />
                        <user name="user2" password="password2" />
                        <user name="user3" password="password3" />
                    </credentials>
                </forms>
            </authentication>
            <authorization>
                <deny users="?"/>
            </authorization>
        </system.web>
    </configuration>
  • В проводнике решений вновь вызовите контекстное меню для страницы Default.aspx или MyDefault.aspx и выполните команду View in Browser. Убедитесь, что ASP.NET теперь не разрешает пользоваться этими страницами анонимно и пытается адресоваться к странице регистрации, назначенной нами в конфигурационном файле параметром loginUrl="MyLogin.aspx"
  • Создание страницы регистрации

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

    Страница регистрации должна включать в себя поля ввода для получения от пользователя данных удостоверения. Для проверки достоверности пользовательских данных необходимо использовать валидаторы, генерирующие нужный JavaScript -код на клиенте, а также выполняющие проверку на сервере.

  • Создайте заготовку новой страницы с именем MyLogin.aspx без отделенного кода
  • Заполните страницу элементами управления и настройте их согласно таблице
  • Таблица свойств элементов страницы регистрации MyLogin.aspx
    Элемент Свойство Значение Пояснение
    Panel ID MainPanel Родительская панель для оформления рамкой
      BorderColor Silver Цвет рамки
      BorderStyle Ridge Тип рамки
    TextBox ID UsernameText Текстовое поле ввода имени пользователя
    RequiredFieldValidator ID UsernameRequiredValidator Проверка достоверности, чтобы привязанное поле не было пустым
      ControlToValidate UsernameText Привязка к контролируемому элементу ввода
      Display Dynamic Место под элемент выделять только при визуализации
      ErrorMessage Не заполнено поле "Имя пользователя" Отображать в элементе ValidationSummary при ошибке
      ToolTip Пустое поле ввода Всплывающая подсказка для визуализированного валидатора
      Text * Надпись на визуализированном валидаторе
    RegularExpressionValidator ID UsernameValidator Валидатор регулярных выражений. Следит за вводом только допустимых символов
      ControlToValidate UsernameText Привязка к контролируемому элементу ввода
      Display Dynamic Место под элемент выделять только при визуализации
      ErrorMessage Неверное имя пользователя Отображать в элементе ValidationSummary при ошибке
      ToolTip Допустимы буквы, цифры, пробелы и подчеркивания Всплывающая подсказка для визуализированного валидатора
      ValidationExpression [а-яА-Я\w| ]*

    Регулярное выражение:

  • [] - диапазон пропускаемых символов
  • []* - символы диапазона могут встречаться в контролируемом вводе сколько угодно раз
  • а-яА-Я - разрешать все буквы русского алфавита
  • \w - разрешать все латинские буквы, цифры и знаки подчеркивания. Это управляющий символ регулярного выражения
  • | - знак ИЛИ, после которого стоит пробел, означает, что разрешать и пробелы
  • Таким образом: действительными считаются все русские буквы обоих регистров, все латинские буквы обоих регистров, цифры и знаки подчеркивания, а также пробелы. И все это в любом количестве повторений

      Text * Надпись на визуализированном валидаторе
    TextBox ID PasswordText Текстовое поле ввода пароля
      TextMode Password Ввод отображается звездочками
    RequiredFieldValidator ID PwdRequiredValidator Проверка достоверности, чтобы привязанное поле не было пустым
      ControlToValidate PasswordText Привязка к контролируемому элементу ввода
      Display Dynamic Место под элемент выделять только при визуализации
      ErrorMessage Не заполнено поле "Пароль" Отображать в элементе ValidationSummary при ошибке
      ToolTip Пустое поле ввода Всплывающая подсказка для визуализированного валидатора
      Text * Надпись на визуализированном валидаторе
    RegularExpressionValidator ID PwdValidator Валидатор регулярных выражений. Следит за вводом только допустимых символов
      ControlToValidate PasswordText Привязка к контролируемому элементу ввода
      Display Dynamic Место под элемент выделять только при визуализации
      ErrorMessage Неверный пароль Отображать в элементе ValidationSummary при ошибке
      ToolTip Используются недопустимые символы Всплывающая подсказка для визуализированного валидатора
      ValidationExpression [а-яА-Я\w| !"$amp;/()=\-?\*]* Регулярное выражение:
  • [] - диапазон пропускаемых символов
  • []* - символы диапазона могут встречаться в контролируемом вводе сколько угодно раз
  • а-яА-Я - разрешать все буквы русского алфавита
  • \w - разрешать все латинские буквы, цифры и знаки подчеркивания. Это управляющий символ регулярного выражения
  • | - знак ИЛИ, после которого стоит пробел и ряд других символов, разрешенных для ввода
  • Символы, которые могут интерпретироваться как служебные, экранируются обратной косой чертой. Интерпретируемый HTML-символ амперсанта заменяется неинтерпретируемым аналогом amp;

      Text * Надпись на визуализированном валидаторе
    Button ID Button1 Кнопка Submit
      Text Отправить Надпись
    Label ID lblResult Метка сообщения для заполнения в коде
    ValidationSummary ID ValidationSummary1 Элемент отображения сообщений об ошибках, обнаруженных валидаторами

    Интерфейсная часть страницы MyLogin.aspx в режиме проектирования будет такой

    Код страницы MyLogin.aspx будет таким

    <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        protected void LoginAction_Click(object sender, EventArgs e)
        {
            this.Validate();// Исполнить валидаторы на сервере
            if (!this.IsValid)// Оценить флаг достоверности
                return;// Отправить назад как есть
            
            // Извлечь из web.config и сравнить с введенным 
            if (FormsAuthentication.Authenticate(UsernameText.Text, PasswordText.Text))
            {
                // Создать временный cookie-набор (второй параметр false),
                // записать в него  метку аутентификации и перенаправить
                // на исходную запрошенную страницу или MyDefault.aspx
                FormsAuthentication.RedirectFromLoginPage(UsernameText.Text, false);
            }
            else
            {
                HtmlGenericControl message = new HtmlGenericControl();
                message.InnerHtml = "<h2 style='color: Red'>Неверное имя или пароль!</h2>";
                form1.Controls.Add(message);
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h1>Сегодня <% this.Response.Write(DateTime.Now.ToShortDateString()); %> г.</h1>
                <h2>
                    Введите свои имя и пароль</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table cellpadding="5" style="width: 100%">
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 167px">
                                Имяnbsp;пользователя:</td>
                            <td>
                                nbsp;<asp:TextBox ID="UsernameText" runat="server" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="UsernameRequiredValidator" 
                                    runat="server" ControlToValidate="UsernameText"
                                    Display="Dynamic" 
                                    ErrorMessage='Не заполнено поле "Имя пользователя"' 
                                    ToolTip="Пустое поле ввода">*
                                </asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="UsernameValidator" 
                                    runat="server" ControlToValidate="UsernameText"
                                    Display="Dynamic" ErrorMessage="Неверное имя пользователя" 
                                    ToolTip="Допустимы буквы, цифры, пробелы и подчеркивания"
                                    ValidationExpression="[а-яА-Я\w| ]*">*
                                </asp:RegularExpressionValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 167px">
                                nbsp;Пароль:</td>
                            <td>
                                nbsp;<asp:TextBox ID="PasswordText" runat="server" 
                                    TextMode="Password" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="PwdRequiredValidator" 
                                    runat="server" ControlToValidate="PasswordText"
                                    Display="Dynamic" ErrorMessage='Не заполнено поле "Пароль"' 
                                    ToolTip="Пустое поле ввода">*
                                </asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="PwdValidator" runat="server" 
                                    ControlToValidate="PasswordText"
                                    Display="Dynamic" ErrorMessage="Неверный пароль" 
                                    ToolTip="Используются недопустимые символы"
                                    ValidationExpression='[а-яА-Я\w| !"$amp;/()=\-?\*]*'>*
                                </asp:RegularExpressionValidator>
                            </td>
                        </tr>
                    </table>
                    <asp:Button ID="Button1" runat="server" Text="Отправить" 
                        OnClick="LoginAction_Click" />
                </asp:Panel>
                <asp:Label ID="lblResult" runat="server" />
            </div>
            <asp:ValidationSummary ID="ValidationSummary1" runat="server" />
        </form>
    </body>
    </html>
  • В проводнике решений вызовите контекстное меню для страницы Default.aspx или MyDefault.aspx и выполните команду View in Browser
  • При такой инфраструктуре аутентификации любой пользователь, открывший сеанс с приложением, какую бы страницу он не запросил, будет направляться на страницу регистрации до тех пор, пока не введет свои данные, хранящиеся в удостоверении конфигурационного файла. После совпадения удостоверения будет создан cookie -набор аутентификации со скользящим временем жизни, в котором в зашифрованном виде будет записан мандат, и этот мандат будет действовать на протяжении всего сеанса, пока не истечет допустимое временя простоя сеанса или пока cookie-набор аутентификации не будет нами уничтожен.

    Упражнение 2. Хеширование паролей в конфигурационном файле

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

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

    Параметр passwordFormat может принимать одно из трех следующих значений:

  • Clear - пароль не хеширован и пользовательский пароль нужно сравнивать без предварительного хеширования
  • MD5 - пароль хеширован алгоритмом MD5 и перед сравнением присланный пользовательзователем пароль хешировать тем же алгоритмом
  • SHA1 - для хеширования применять алгоритм SHA1
  • Для предварительного хеширования паролей в Web.config применяется метод FormsAuthentication.HashPasswordForStoringInConfigFile().

  • Добавьте к проекту страницу HashPasswordPage.aspx с встроенным скриптом и наполните ее следующим кодом
  • <%@ Page Language="C#" %>
        
    <%@ Import Namespace="System.Web.Configuration" %>
        
    <script runat="server">
        
        protected void btnHashedPwd_Click(object sender, EventArgs e)
        {
            // Загрузить содержимое корневого Web.config
            Configuration myConfig = System.Web.Configuration.
                WebConfigurationManager.OpenWebConfiguration("~/");
            
            // Найти содержимое секции system.web
            ConfigurationSectionGroup systemWeb = myConfig.SectionGroups["system.web"];
            
            // Найти содержимое секции authentication 
            AuthenticationSection authSec =
                (AuthenticationSection)
                systemWeb.Sections["authentication"];
            
            // Установить параметр passwordFormat, сообщающий, что было хеширование по MD5
            authSec.Forms.Credentials.PasswordFormat = FormsAuthPasswordFormat.MD5;
            
            // Извлекаем из коллекции Users старые имена и пароли 
            int count = authSec.Forms.Credentials.Users.Count;
            string[] name = new string[count];
            string[] clearTextPwd = new string[count];
            int i = 0;
            foreach (FormsAuthenticationUser user in authSec.Forms.Credentials.Users)
            {
                name[i] = user.Name;
                clearTextPwd[i] = user.Password;
                i++;
            }
            
            // Очищаем коллекцию Users 
            authSec.Forms.Credentials.Users.Clear();
            
            // Добавляем старые имена и новые хешированные пароли
            for (i = 0; i < count; i++)
            {
                // Хешируем исходный пароль
                string hashedPwd = FormsAuthentication.
                    HashPasswordForStoringInConfigFile(clearTextPwd[i], "MD5");
                // Добавляем в коллекцию Users
                authSec.Forms.Credentials.Users.Add(
                    new FormsAuthenticationUser(name[i], hashedPwd));
            }
            
            // Обновляем файл Web.config 
            myConfig.Save();
            
            // Отсылаем сообщение 
            lblHashResult.Text = "Хеширование завершено.<br />"
                + "Повторно не выполнять - будет хеш на хеш!!!";
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h1>
                    Административная страница хеширования паролей в Web.config</h1>
                <p>
                    <asp:Button ID="btnHashedPwd" runat="server" 
                                OnClick="btnHashedPwd_Click" Text="Хешировать" /></p>
                <p>
                    <asp:Label ID="lblHashResult" runat="server"></asp:Label></p>
            </div>
        </form>
    </body>
    </html>
  • Выполните страницу HashPasswordPage.aspx, но только один раз, чтобы повторно не хешировать уже хешированные пароли
  • В результате мы получим вариант конфигурационного файла с хешированными паролями

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName" 
                    loginUrl="MyLogin.aspx" 
                    defaultUrl="MyDefault.aspx"
                    protection="All"
                    timeout="20"
                    path="/" 
                    requireSSL="false"
                    slidingExpiration="true"
                    cookieless="AutoDetect"
                    domain=""
                    enableCrossAppRedirects="false"
                >
                    <credentials passwordFormat="MD5">
                        <user name="admin" password="FA03EB688AD8AA1DB593D33DABD89BAD" />
                        <user name="петя" password="B63974E3EE7B0920FE24DFB8E82FAE81" />
                        <user name="вася" password="44D4B2D42E830BE0DD43E6830D957C5D" />
                        <user name="user1" password="7C6A180B36896A0A8C02787EEAFB0E4C" />
                        <user name="user2" password="6CB75F652A9B52798EB6CF2201057C73" />
                        <user name="user3" password="819B0643D6B89DC9B579FDFC9094F28E" />
                    </credentials>
                </forms>
            </authentication>
            <authorization>
                <deny users="?"/>
            </authorization>
        </system.web>
    </configuration>
  • Откройте в редакторе оболочки страницу Default.aspx и командой File/View in Browser запустите ее на выполнение
  • Вначале будет предложена страница регистрации, поскольку конфигурация сайта настроена именно так.

  • Введите в странице регистрации MyLogin.aspx учетную запись user1 и password1
  • Исходные пароли, вводимые пользователями при регистрации, остались прежними. Но в конфигурационном файле они теперь храняться как хешированные по алгоритму MD5 и являются бесполезными для злоумышленников. Сама же система при аутентификации приводит исходные пароли перед сравнением к хешированному виду по тому же самому алгоритму. Если исходный и оригинальный пароли правильные, то их хеши будут совпадать.

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

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

    Упражнение 3. Закрепление за броузером постоянной аутентификации форм

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

    Постоянные cookie-набор можно создать

  • в странице регистрации при первой посещении пользователя установкой второго параметра в значение true (вместо false) в методе

    FormsAuthentication.RedirectFromLoginPage( строка_имя , true);

  • в любом другом месте открытого сеанса выполнением метода

    FormsAuthentication.SetAuthCookie( строка_имя , true);

  • в любом другом месте открытого сеанса выполнением кода

    HttpCookie authCookie = FormsAuthentication.GetAuthCookie( строка_имя , true);

    this.Response.Cookies.Add(authCookie);

  • Установленный таким способом cookie-набор будет существовать на компьютере пользователя "вечно", если только мы не удалим его методом

    FormsAuthentication.SignOut();

    Можно также изменить временной статус постоянного cookie-набора на любой странице. Покажем это

  • Добавьте к проекту страницу ModifyPersistentCookieAuth.aspx без файла отделенного кода и заполните ее так
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        Label message;
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Дескриптор центрирования
            HtmlGenericControl center = new HtmlGenericControl("center");
            form1.Controls.Add(center);
            
            // Текстовая метка с заголовком 
            Label label = new Label();
            center.Controls.Add(label);
            label.Text = "<h2>Закрепить аутентификацию за броузером</h2>";
            
            // Кнопки
            Button createPersistentCookie = new Button();
            center.Controls.Add(createPersistentCookie);
            createPersistentCookie.Text = "Создать бессрочный AuthCookie";
            createPersistentCookie.Click += new EventHandler(createPersistentCookie_Click);
            center.Controls.Add(new HtmlGenericControl("br"));
            
            Button createTemporaryCookie = new Button();
            center.Controls.Add(createTemporaryCookie);
            createTemporaryCookie.Text = "Создать срочный AuthCookie";
            createTemporaryCookie.Click += new EventHandler(createTemporaryCookie_Click);
            center.Controls.Add(new HtmlGenericControl("br"));
        
            Button deleteCookie = new Button();
            center.Controls.Add(deleteCookie);
            deleteCookie.Text = "Удалить AuthCookie";
            deleteCookie.Click += new EventHandler(deleteCookie_Click);
            center.Controls.Add(new HtmlGenericControl("br"));
            
            // Текстовая метка с сообщением
            message = new Label();
            center.Controls.Add(message);
            message.Text = String.Empty;
        }
        
        void createPersistentCookie_Click(object sender, EventArgs e)
        {
            // Создать бессрочный cookie
            HttpCookie authCookie = FormsAuthentication.GetAuthCookie("xx", true);
            
            // Отослать на броузер
            this.Response.Cookies.Add(authCookie);
            
            // Сообщить пользователю
            message.Text = "Бессрочный AuthCookie создан!";
        }
        
        void createTemporaryCookie_Click(object sender, EventArgs e)
        {
            // Создать вначале бессрочный cookie
            HttpCookie authCookie = FormsAuthentication.GetAuthCookie("xx", true);
            
            // Настроить его как временный сроком на 10 дней
            authCookie.Expires = DateTime.Now.AddDays(10);
        
            // Отослать на броузер
            this.Response.Cookies.Add(authCookie);
        
            // Сообщить пользователю
            message.Text = "Срочный AuthCookie создан!";
        }
        
        void deleteCookie_Click(object sender, EventArgs e)
        {
            // Удалить cookie-набор регистрации
            FormsAuthentication.SignOut();
        
            // Сообщить пользователю
            message.Text = "AuthCookie удален!";
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        </form>
    </body>
    </html>

    Интерфейс страницы в режиме выполнения будет таким

    При закреплении cookie-набора за броузером при соединении с сайтом страница регистрации пользователю предъявляться не будет, пока не закончится срок действия (если cookie-набор срочный).

    API Membership является готовым механизмом в ASP.NET, предназначенным для организации управления аутентификацией пользователей. Он базируется на аутентификации форм. Единственное отличие, что хранение пользователей будет осуществляться не в конфигурационном файле, а в базе данных. И для управления задействуются классы Membership.

    Чаще всего к корневому каталогу сайта допускаются все анонимные пользователи без регистрации, а защищенные ресурсы, требующие аутентификацию, размещаются в подкаталогах со своими файлами Web.config. Если кто-то запрашивает защищенный ресурс из подкаталога, система автоматически перенаправляет пользователя на страницу регистрации.

    Для использования API Membership нужно выполнитиь следующие шаги:

  • Сконфигурировать аутентификацию форм в корневом каталоге приложения и авторизацию в защищенных каталогах
  • Создать и настроить базу данных для хранения удостоверений пользователей
  • Занести в хранилище пользователей
  • Создать страницу регистрации
  • Упражнение 4. Создание хранилища удостоверений

    SQL Server 2005 создает базу данных и работает с ней как с отдельным файлом с расширением .mdf. По умолчанию файлы баз данных размещаются в каталоге

    C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data

    Мы сами можем вручную создать файл базы данных для хранения удостоверений и добавить в нее таблицы с необходимыми полями. Но это может несогласоваться с механизмом API Membership, поскольку он ожидает базу данных определенной структуры. К тому же нужно будет прописывать в конфигурационном файле строку соединений с этой базой и другие конфигурационные параметры, необходимые для работы API Membership. Более простой вариант предусмотрели разработчики Visual Studio 2005, включив поддержку Membership в утилиту Web Site Administration Tool ( WAT ). Теперь база данных нужной структуры для хранения удостоверений будет создана автоматически при первом запуске WAT, если в конфигурационном файле прописана секция аутентификации форм.

  • Откройте файл web.config и удалите из него секцию <credentials> вместе с внесенными ранее пользователями и временно закомментируйте секцию авторизации. При этом должна остаться секция аутентификации форм
  • В результате конфигурационный файл должен стать таким

    <?xml version="1.0" encoding="utf-8"?>
    <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
      <system.web>
        <compilation debug="true" />
        <authentication mode="Forms">
          <forms name="MyCookieName"
            loginUrl="MyLogin.aspx"
            defaultUrl="MyDefault.aspx"
            protection="All"
            timeout="20"
            path="/"
            requireSSL="false"
            slidingExpiration="true"
            cookieless="AutoDetect"
            domain=""
            enableCrossAppRedirects="false">
          </forms>
        </authentication>
        <!-- Временно закомментировали, чтобы не требовал регистрации
        <authorization>
          <deny users="?"/>
        </authorization>
        -->
      </system.web>
    </configuration>
  • Запустите утилиту WAT командой Website/ASP.NET Configuration и обратите внимание, как долго она запускается
  • Замечание. Эта утилита может не запускаться, если абсолютный путь к созданному проекту имеет пробелы или кириллицу. В этом случае переместите проект в другое место файловой системы компьютера.

  • В панели Solution Explorer щелкните на кнопке Refresh
  • Мы видим, что утилита WAT так долго запускалась потому, что добавила к проекту каталог App_Data (если до этого его еще не было) и создала в нем базу данных ASPNETDB.MDF, готовую для хранения удостоверений и согласованную с API Membership

  • В панели Solution Explorer двойным щелчком на файле ASPNETDB.MDF откройте соединение с этой базой данных и ознакомтесь в панели Server Explorer с ее структурой, типичной для SQL Server
  • Такой способ хранения удостоверений пользователей в локальной базе данных вместе с приложением очень удобен, поскольку позволяет перемещать сайт в любое место сервера или разворачивать на любом сервере без дополнительных настроек подключения SQL Server. Да и структуру базы данных утилита WAT создала такой, какая нужна системе API Membership. Это значит, что все классы Membership будут согласованы с хранилищем удостоверений.

    Утилита WAT автоматически создает хранилище удостоверений только в том случае, если обнаружит в корневом конфигурационном файле секцию

    <authentication mode="Forms">

    Если на момент запуска утилиты WAT файл конфигурации в корневом каталоге приложения отсутствует или в нем нет секции аутентификации на основе форм, то утилита базу данных не создает.

    Для того, чтобы проверить работоспособность созданной локальной базы данных, выполните следующее:

  • Запустите утилиту WAT командой Website/ASP.NET Configuration
  • На вкладке Home щелкните на гиперссылке Provider Configuration, чтобы перейти на вкладку Provider
  • На вкладке Provider щелкните на первой гиперссылке " Select a single provider for all site management data "
  • В отклике, сгенерированном административной страницей ManageConsolidatedProviders.aspx, щелкните на гиперссылке Test
  • В результате страница пришлет ответ, что созданная нами локальная база данных функционирует нормально.

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

    Добавление в хранилище БД новых пользователей через утилиту WAT

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

    Для создания новых аутентифицированных пользователей через утилиту WAT выполните следующее:

  • В панели Solution Explorer щелкните на пиктограмме ASP.NET Configuration, чтобы запустить утилиту WAT
  • Перейдите на вкладку Security и щелкните на гиперссылке Create user
  • Появится следующий отклик утилиты WAT

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

    В утилите за полем ввода пароля закреплен валидатор для контроля соблюдения требований к строгости пароля. По умолчанию настройка валидаторов пароля такая, что пароль должен иметь длину не менее 7 символов. Причем, как минимум один из символов должен быть неалфавитно-цифровым, например, символ . Это обеспечивает минимально необходимую строгость пароля. Позднее мы рассмотрим, как в конфигурационном файле можно менять принятые по умолчанию настройки Membership, которые, в том числе, будут влиять и на поведение WAT.

  • Заполните поля страницы для добавления в хранилище одного пользователя со следующими характеристиками
  • User Name: admin
    Password: RootRoot
    Confirm Password: RootRoot
    E-mail: Dialog-Service@List.ru
    Security Question: У Вас продается славянский шкаф?
    Security Answer: Шкаф уже продан, осталась только тумбочка!

    и щелкните на кнопке Create User

    После этого действия новый пользователь будет создан в базе данных.

  • В появившемся отклике щелкните на кнопке Continue, чтобы вернутся к интерфейсу добавления следующего пользователя, либо закройте WAT
  • Страница регистрации при использовании Membership

    На данном этапе для проверки аутентификации с использованием API Membership и хранилища БД мы воспользуемся страницей регистрации MyLogin.aspx. Ее нужно только немного модифицировать.

  • Модифицируйте страницу регистрации MyLogin.aspx, чтобы код стал таким
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        protected void LoginAction_Click(object sender, EventArgs e)
        {
            this.Validate();// Исполнить валидаторы на сервере
            if (!this.IsValid)// Оценить флаг достоверности
                return;// Отправить назад как есть
            
            // Извлечь из базы данных и сравнить с введенным 
            if (Membership.ValidateUser(UsernameText.Text, PasswordText.Text))
            {
                // Создать временный cookie-набор (второй параметр false),
                // записать в него  метку аутентификации и перенаправить
                // на исходную запрошенную страницу или defaultUrl="MyDefault.aspx"
                // с созданием coockie-набора только на период сеанса
                FormsAuthentication.RedirectFromLoginPage(UsernameText.Text, false);
            }
            else
            {
                HtmlGenericControl message = new HtmlGenericControl();
                message.InnerHtml = "<h2 style='color: Red'>Неверное имя или пароль!</h2>";
                form1.Controls.Add(message);
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h1>Сегодня <% this.Response.Write(DateTime.Now.ToShortDateString()); %> г.</h1>
                <h2>
                    Введите свои имя и пароль</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table cellpadding="5" style="width: 100%">
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 167px">
                                Имяnbsp;пользователя:</td>
                            <td>
                                nbsp;<asp:TextBox ID="UsernameText" runat="server" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="UsernameRequiredValidator" 
                                    runat="server" ControlToValidate="UsernameText"
                                    Display="Dynamic" 
                                    ErrorMessage='Не заполнено поле "Имя пользователя"' 
                                    ToolTip="Пустое поле ввода">*
                                </asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="UsernameValidator" 
                                    runat="server" ControlToValidate="UsernameText"
                                    Display="Dynamic" ErrorMessage="Неверное имя пользователя" 
                                    ToolTip="Допустимы буквы, цифры, пробелы и подчеркивания"
                                    ValidationExpression="[а-яА-Я\w| ]*">*
                                </asp:RegularExpressionValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 167px">
                                nbsp;Пароль:</td>
                            <td>
                                nbsp;<asp:TextBox ID="PasswordText" runat="server" 
                                    TextMode="Password" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="PwdRequiredValidator" 
                                    runat="server" ControlToValidate="PasswordText"
                                    Display="Dynamic" ErrorMessage='Не заполнено поле "Пароль"' 
                                    ToolTip="Пустое поле ввода">*
                                </asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="PwdValidator" runat="server" 
                                    ControlToValidate="PasswordText"
                                    Display="Dynamic" ErrorMessage="Неверный пароль" 
                                    ToolTip="Используются недопустимые символы"
                                    ValidationExpression='[а-яА-Я\w| !"$amp;/()=\-?\*]*'>*
                                </asp:RegularExpressionValidator>
                            </td>
                        </tr>
                    </table>
                    <asp:Button ID="Button1" runat="server" Text="Отправить" 
                        OnClick="LoginAction_Click" />
                </asp:Panel>
                <asp:Label ID="lblResult" runat="server" />
            </div>
            <asp:ValidationSummary ID="ValidationSummary1" runat="server" />
        </form>
    </body>
    </html>
  • Запустите приложение с любой страницы и введите учетную запись, сохраненную нами ранее в базе данных
  • User Name: admin
    Password: RootRoot
  • С помощью утилиты WAT добавьте в хранилище еще несколько пользователей и убедитесь, что аутентификация на основе Membership и хранилища удостоверений в БД работает
  • При добавлении новых пользователей следует иметь ввиду, что имя пользователя сохраняется в поле UserName реляционной таблицы aspnet_Users и является основой для формирования поля UserId как первичного ключа. Поэтому все пользователи должны иметь уникальные имена и регистр символов не имеет значения.

  • В панели Server Explorer установите соединение с базой данных ASPNETDB двойным щелчком указателя мыши на пиктограмме базы
  • Для таблиц aspnet_Users и aspnet_Membership выполните команду Show Table Data контекстного меню
  • После таких действий откроются таблицы aspnet_Users и aspnet_Membership, в которых можно найти информацию о созданных пользователях. Для того, чтобы увидеть структуру таблиц, следует выполнить для них команду Open Table Definition контекстного меню.

    Административные страницы управления аутентификацией с использованием API Membership

    Интерфейс API Membership включает в себя следующие классы пространства имен System.Web.Security:

  • Membership - статический класс, имеющий ряд полезных методов управления пользователями
  • MembershipCreateUserException - исключение, генерируемое ошибкой создания пользователя (например, с уже существующим именем)
  • MembershipPasswordException - исключение при невозможности извлечения пароля
  • MembershipUser - объект этого класса содержит всю информацию о пользователе и возвращается методом Membership.GetUser()
  • MembershipUserCollection - коллекция пользователей, извлекаемая методом Membership.GetAllUsers()
  • MembershipProvider - базовый класс для поставщиков данных
  • MembershipProviderCollection - коллекция доступных для приложения поставщиков на данной машине
  • SqlMembershipProvider - поставщик для работы с базами данных SQL Server
  • ActiveDirectoryMembershipProvider - поставщик для работы со службой Active Directory
  • ActiveDirectoryMembershipUser - класс поддержки пользователя, расширенный для работы со службой Active Directory
  • Используя API Membership, можно программно решать, например, следующие задачи:

  • Создание, редактирование и удаление пользователей
  • Извлечение списков пользователей со всей подробной информацией, занесенной при создании
  • Упражнение 5. Административная страница создания пользователей в БД

    Для создания и удаления пользователей следует применить перегруженные статические методы CreateUser() и DeleteUser() класса Membership. Нужно создать административные страницы, содержащие требуемые поля для ввода информации. Поля можно контролировать валидаторами, хотя это и необязательно, поскольку эти страницы административные, пользоваться такими страницами будут администраторы, которые будут строго соблюдать правила ввода.

    Метод CreateUser() имеет несколько перегрузок, из которых простейшая принимает только имя и пароль. В то же время более полные перегрузки требуют ввода контрольного вопроса и ответа на него. В самой полной перегрузке метод CreateUser() содержит экземпляр перечисления MembershipCreateStatus в качестве выходного параметра, позволяющий проанализировать причину ошибки в случае ее возникновения. По умолчанию требуется применять наиболее полную перегрузку метода CreateUser(), включающую контрольный вопрос и ответ, чтобы избежать выброса исключения MembershipCreateUserException.

    Удаление пользователя выполняется методом Membership.DeleteUser(string), которому передается в качестве параметра уникальное имя пользователя. Пользователь безвозвратно удаляется из всех таблиц вместе со всей связанной с ним информацией.

    В качестве примера создадим собственную страницу добавления, имеющую аналогичную функциональность с WAT, и отдельно страницу удаления пользователя в БД. Для простоты эти страницы мы переделаем из страницы регистрации MyLogin.aspx.

  • Скопируйте страницу MyLogin.aspx и присвойте копии имя AddUserMembership.aspx
  • Провайдер базы данных не добавляет новых пользователей, если пароль имеет длину менее 7 символов и если среди них нет хотя-бы одного неалфавитно-цифрового символа. Для проверки выполнения этого условия, а также для тренировки, закрепим за полем пароля кодируемый элемент проверки достоверности CustomValidator. С помощью этого элемента реализуем проверку достоверности на клиенте и на сервере.

    Неалфавитно-цифровые символы представим в виде ASCII -кодов согласно следующей таблице

    Набор неалфавитно-цифровых символов ASCII
      0 1 2 3 4 5 6 7 8 9
    0                   
    1                   
    2                   
    3      ! " # $ % '
    4( ) * + , . /      
    5                : ;
    6< = > ? @          
    7                   
    8                   
    9  [ \ ] ^ _        
    10                   
    11                   
    12      { | } ~      

    Строка ASCII -кодов неалфавитно-цифровых символов на JavaScript будет такой:

    var nonAlphanumericCharactersString = String.fromCharCode(33,34,35,36,37,38,39,40,41,42,43,44,45,46,                                 58,59,60,61,62,63,64,91,92,93,94,95,123,124,125,126);

  • Переделайте интерфейсную и скриптовую части страницы, чтобы она стала такой
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        // Блок на C# по умолчанию 
        protected void Page_Load(object sender, EventArgs e)
        {
            // Вынесли из интерфейсной части из-за большой ширины листинга
            passwordValidator.ErrorMessage = 
                "Пароль должен иметь длину не менее 7 символов"
                + " и содержать хотя бы один неалфавитно-цифровой символ";
        }
        
        protected void AddUserMembership_Click(object sender, EventArgs e)
        {
            this.Validate();// Исполнить валидаторы на сервере
            if (!this.IsValid)// Оценить флаг достоверности
                return;// Отправить назад как есть
        
            // Создаем экземпляр перечисления
            MembershipCreateStatus status;
        
            // Попытка добавить
            try
            {
                Membership.CreateUser(
                    username.Text,
                    password.Text,
                    email.Text,
                    question.Text,
                    answer.Text,
                    true,
                    out status
                    );
        
                lblResult.Text = String.Empty;
                switch (status)
                {
                    case MembershipCreateStatus.DuplicateUserName:
                        lblResult.Text = "Дублированное имя пользователя<br/>";
                        goto default;
                    case MembershipCreateStatus.ProviderError:
                        lblResult.Text = "Ошибка Поставщика<br/>";
                        goto default;
                    default: // Дальше лень анализировать
                        lblResult.Text += "Пользователь не создан";
                        lblResult.ForeColor = System.Drawing.Color.Red;
                        break;
                    case MembershipCreateStatus.Success:
                        // Выдаем успешную информацию
                        lblResult.Text = "Пользователь " + username.Text 
                            + " создан успешно!";
                        // Рекурсивно очищаем все текстовые поля ввода
                        EmptyTextBox(form1.Controls);
                        lblResult.Text += "<br/>Поля ввода очищены";
                        break;
                }
            }
            // Откат
            catch (MembershipCreateUserException ex)
            {
                lblResult.Text = "<h2 style='color: Red'>Ошибка создания пользователя</h2>";
                System.Diagnostics.Debug.WriteLine("Exception: "
                    + ex.Message);
            }
        }
        
        private void EmptyTextBox(ControlCollection controls)
        {
            foreach (Control ctrl in controls)
            {
                if (ctrl is TextBox)
                    ((TextBox)ctrl).Text = String.Empty;
        
                // Идем в глубину иерархической цепочки и ищем дочерние элементы
                if (ctrl.Controls != null)
                    EmptyTextBox(ctrl.Controls);
            }
        }
    </script>
        
    <script language="C#" runat="server">
    // Еще один блок с явным указанием C#, хотя можно атрибут языка и не указывать
        
        // Проверка допустимости пароля на сервере 
        // Обработчик события ServerValidate элемента CustomValidator
        protected void passwordValidator_ServerValidate(object source, ServerValidateEventArgs args)
        {
            string data = args.Value;
        
            // Установить начальное значение флага достоверности
            args.IsValid = false;
        
            // Проверить длину пароля 
            if (data.Length < 7)
                return;
        
            // Проверить наличие неалфавитноцифрового символа
            // Формируем массив неалфавитноцифровых символов по их кодам,
            // чтобы не связываться с интерпретируемыми символами
            int[] nonAlphanumericCharactersCode =
                {33,34,35,36,37,38,39,40,41,42,43,44,45,46,
                 58,59,60,61,62,63,64,91,92,93,94,95,123,124,125,126};
            
            // Перебираем символы пользовательского ввода
            foreach (char ch in data)
            {
                for (int i = 0; i < nonAlphanumericCharactersCode.Length; i++)
                    if (ch == Convert.ToChar(nonAlphanumericCharactersCode[i]))
                    {
                        // Пароль допустим 
                        args.IsValid = true;
                        return;
                    }
            }
        }
    </script>
        
    <script language="javascript" type="text/javascript">
        // Проверка допустимости пароля на клиенте. Работает при включенном
        // по умолчанию свойстве EnableClientScript элемента CustomValidator
            
        function ValidatePassword(source, args)
        {
            // Расщепить строку на массив символов с пустым разделителем
            var data = args.Value.split("");
            
            // Установить начальное значение флага достоверности
            args.IsValid = false;
            
            // Проверить длину пароля 
            if(data.length < 7)
                return;
                
            // Проверить наличие неалфавитноцифрового символа
            // Формируем строку неалфавитноцифровых символов по их кодам,
            // чтобы не связываться с интерпретируемыми символами
            var nonAlphanumericCharactersString = 
                String.fromCharCode(33,34,35,36,37,38,39,40,41,42,43,44,45,46,
                                    58,59,60,61,62,63,64,91,92,93,94,95,123,124,125,126);             
            for(var ch in data)
            {
                if(nonAlphanumericCharactersString.indexOf(data[ch]) != -1)
                {
                    // Пароль допустим 
                    args.IsValid = true;           
                    return;
                }
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Добавление пользователей в БД</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h2 id="H2_1" runat="server">
                    Введите учетную запись для добавления пользователя</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="364px">
                    <table cellpadding="5" style="width: 137%">
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                Имяnbsp;пользователя:
                            </td>
                            <td>
                                nbsp;<asp:TextBox ID="username" runat="server" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="usernameRequiredValidator" 
                                    runat="server" ControlToValidate="username" 
                                    ErrorMessage='Не заполнено поле "Имя пользователя"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                nbsp;Пароль:
                            </td>
                            <td>
                                nbsp;<asp:TextBox ID="password" runat="server" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="passwordRequiredValidator" 
                                    runat="server" ControlToValidate="password" 
                                    ErrorMessage='Не заполнено поле "Пароль"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                                <asp:CustomValidator ID="passwordValidator" runat="server" 
                                    ControlToValidate="password"
                                    Display="Dynamic" 
                                    ClientValidationFunction="ValidatePassword" 
                                    OnServerValidate="passwordValidator_ServerValidate">*</asp:CustomValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                E-mail:</td>
                            <td>
                                nbsp;<asp:TextBox ID="email" runat="server"></asp:TextBox>
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="emailRequiredValidator" 
                                    runat="server" ControlToValidate="email" 
                                    ErrorMessage='Не заполнено поле "E-mail"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="emailValidator" runat="server" 
                                    ControlToValidate="email" ErrorMessage="Неверный E-mail" 
                                    ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" 
                                    Display="Dynamic">*</asp:RegularExpressionValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                Контрольный вопрос:</td>
                            <td>
                                nbsp;<asp:TextBox ID="question" runat="server"></asp:TextBox>
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="questionRequiredValidator" 
                                    runat="server" ControlToValidate="question" 
                                    ErrorMessage='Не заполнено поле "Контрольный вопрос"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                Контрольный ответ:</td>
                            <td>
                                nbsp;<asp:TextBox ID="answer" runat="server"></asp:TextBox>
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="answerRequiredValidator" 
                                    runat="server" ControlToValidate="answer" 
                                    ErrorMessage='Не заполнено поле "Контрольный ответ"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                            </td>
                        </tr>
                        <tr>
                            <td colspan="4" align="center">
                                <asp:Button ID="btnAddUser" runat="server" 
                                    Text="Добавить" OnClick="AddUserMembership_Click" />
                            </td>
                        </tr>
                    </table>
                </asp:Panel>
                <asp:Label ID="lblResult" runat="server" Font-Bold="True" 
                    Font-Size="Large" ForeColor="Blue" />
            </div>
            <asp:ValidationSummary ID="ValidationSummary1" runat="server" />
        </form>
    </body>
    </html>
  • Выполните страницу AddUserMembership.aspx и убедитесь в работоспособности рассматриваемого механизма добавления пользователей в БД SQL Server
  • Упражнение 6. Административная страница удаления пользователей из БД

    Теперь рассмотрим вопрос удаления пользователей. Он намного проще, чем создание. Нужно только применить метод System.Web.Security.Membership.DeleteUser(), который имеет две перегрузки:

  • public static bool DeleteUser( string username ) - удаляет по имени пользователя все связанные с ним данные из всех таблиц БД
  • public static bool DeleteUser( string username, bool deleteAllRelatedData ) - удаляет пользователя только из таблицы aspnet_Users, если флаг равен false. Иначе удаляет все связанные с пользователем данные при флаге равном true
  • Обе перегрузки возвращают true, если пользователь успешно удален, иначе false.

  • Разработайте из копии страницы MyLogin.aspx административную страницу удаления пользователей DeleteUserMembership.aspx, которая будет примерно такой
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        protected void DeleteUser_Click(object sender, EventArgs e)
        {
            if (Membership.DeleteUser(username.Text))
            {
                lblResult.Text="Пользователь " + username.Text 
                    + " успешно удален";
                username.Text = "";
            }
            else
            {
                lblResult.Text = "Пользователь не удален";
                lblResult.ForeColor = System.Drawing.Color.Red;
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Удаление пользователей из БД</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h2>
                    Введите имя удаляемого пользователя</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table cellpadding="5" style="width: 100%">
                        <tr>
                            <td align="right" height="43" style="width: 167px">
                                Имяnbsp;пользователя:</td>
                            <td>
                                nbsp;<asp:TextBox ID="username" runat="server" />
                            </td>
                        </tr>
                    </table>
                    <asp:Button ID="Button1" runat="server" Text="Удалить" 
                        OnClick="DeleteUser_Click" /></asp:Panel>
                <asp:Label ID="lblResult" runat="server" Font-Bold="True" 
                    Font-Size="Large" ForeColor="Blue">
                </asp:Label>
            </div>
        </form>
    </body>
    </html>
  • Испытайте страницу удаления пользователей
  • Упражнение 7. Административная страница извлечения списков пользователей из БД

    Для получения списка пользователей применяют метод Membership.GetAllUsers(), а для извлечения конкретного пользователя - метод Membership.GetUser().

    Рассмотрим пример, в котором будем использовать элемент GridView для представления пользователей в виде таблицы.

  • Добавьте к проекту страницу GetUsersMembership.aspx без файла отделенного кода и назначьте ее стартовой
  • Заполните страницу следующим кодом
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        // Вынесли поле класса для видимости в обработчиках
        MembershipUserCollection myUsers = new MembershipUserCollection();
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Скрываем таблицу деталей
            tableDetails.Visible = false;
            lblUser.Visible = false;
        
            if (checkUser.Checked)
            {
                // Извлекаем одного пользователя
                if (txtUser.Text.Length == 0)
                {
                    lblUsersList.Text = "Введите имя пользователя";
                    return;
                }
        
                try // Попытка
                {
                    string user = Server.HtmlEncode(txtUser.Text);
                    myUsers = new MembershipUserCollection();
                    myUsers.Add(Membership.GetUser(user));
                }
                catch // Откат
                {
                    lblUsersList.Text = "Пользователь не найден";
                    return;
                }
            }
            else
            {
                // Извлекаем всех пользователей
                myUsers = Membership.GetAllUsers();
            }
        
            // Подключаем к сетке извлеченную информации и наполняем элемент
            UsersGridView.DataSource = myUsers;
            UsersGridView.DataBind();
        }
        
        // Обработчик щелчков на ссылки столбца отображения деталей
        protected void UsersGridView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (UsersGridView.SelectedIndex >= 0)
            {
                // Заполняем интерфейсные элементы отображаемой информацией,
                // адресуясь к конкретному пользователю по ключевому имени,
                // заданному параметром DataKeyNames="UserName" элемента GridView 
                MembershipUser currentUser = 
                    myUsers[(string)UsersGridView.SelectedValue];
                lblUserName.Text = currentUser.UserName;
                lblEmail.Text = currentUser.Email;
                lblPasswordQuestion.Text = currentUser.PasswordQuestion;
                lblLastLoginDate.Text = currentUser.LastLoginDate.ToLongDateString();
                txtComment.Text = currentUser.Comment;
                checkIsApproved.Checked = currentUser.IsApproved;
                checkIsLockedOut.Checked = currentUser.IsLockedOut;
                
                // Включаем видимость таблицы деталей
                tableDetails.Visible = true;
                lblUser.Visible = true;
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <h2>
                    <asp:Label ID="lblUsersList" runat="server" Font-Bold="true" 
                        Text="Список зарегистрированных пользователей"></asp:Label>
                </h2>
                <p>Конкретный пользователь: 
                    <asp:CheckBox ID="checkUser" runat="server" />
                    <asp:TextBox ID="txtUser" runat="server"></asp:TextBox>
                    <asp:Button ID="btnUser" runat="server" Text="Получить" /></p>
                <asp:GridView ID="UsersGridView" runat="server" 
                    DataKeyNames="UserName" 
                    AutoGenerateColumns="False" 
                    OnSelectedIndexChanged="UsersGridView_SelectedIndexChanged">
                    <Columns>
                        <asp:BoundField DataField="UserName" HeaderText="Имя" />
                        <asp:BoundField DataField="Email" HeaderText="email" />
                        <asp:BoundField DataField="CreationDate" HeaderText="Создан" />
                        <asp:CommandField ShowSelectButton="True" HeaderText="Детали" 
                            SelectText="Показать" />
                    </Columns>
                </asp:GridView>
                <h2>
                    <asp:Label ID="lblUser" runat="server" 
                        Text="Подробности для выделенного пользователя"
                        Visible="False" Font-Bold="true"></asp:Label>
                </h2>            
                <table id="tableDetails" bordercolor="#0000FF" 
                    runat="server">
                    <tr>
                        <td width="50%">
                            Пользователь:</td>
                        <td>
                            <asp:Label ID="lblUserName" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            email:</td>
                        <td>
                            <asp:Label ID="lblEmail" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Контрольный вопрос:</td>
                        <td>
                            <asp:Label ID="lblPasswordQuestion" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Дата последней регистрации:</td>
                        <td>
                            <asp:Label ID="lblLastLoginDate" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Комментарии:</td>
                        <td>
                            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" 
                               ></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Проверенный:</td>
                        <td>
                            <asp:CheckBox ID="checkIsApproved" runat="server" /></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Заблокирован:</td>
                        <td>
                            <asp:CheckBox ID="checkIsLockedOut" runat="server" /></td>
                    </tr>
                </table>
            </div>
        </form>
    </body>
    </html>

    Интерфейсная часть страницы представлена элементом GridView и текстовыми метками, спозиционированными в серверной таблице. Значения параметров DataField элемента GridView представляют собой имена полей таблиц aspnet_Users и aspnet_Membership базы данных

    Установка в элементе управления GridView свойства DataKeyNames="UserName" позволяет извлекать значения поля UserName через GridView.SelectedValue, что очень удобно при получении подробностей о конкретном пользователе из коллекции пользователей.

    Метод Membership. GetUser() возвращает объект запрошенного пользователя, свойства которого мы отображаем в текстовых элементах, скомпонованных в таблице. Привязка к свойствам объекта пользователя выполняется в обработчике события SelectedIndexChanged элемента управления GridView.

    Результат работы страницы GetUsersMembership.aspx будет примерно таким

    Упражнение 8. Административная страница обновления пользователей в БД

    Если у нас есть экземпляр класса MembershipUser, то мы можем обновить его свойства, имеющие аксессор set, а именно:

  • Comment
  • Email
  • IsApproved
  • LastActivityDate
  • LastLoginDate
  • Для этого используется статический метод Membership. UpdateUser().

  • Получите из страницы GetUsersMembership.aspx копию с именем UpdateUserMembership.aspx
  • Скорректируйте страницу UpdateUserMembership.aspx, чтобы обновляемые параметры таблицы деталей размещались в текстовых полях
  • Закрепите за полем txtEmail элементы проверки достоверности RequiredFieldValidator и RegularExpressionValidator
  • Окончательный код страницы UpdateUserMembership.aspx будет таким

    <%@ Page Language="C#" EnableViewState="false" %>
    <%@ Import Namespace="System.Web.Security" %>
        
    <script runat="server">
        
        // Вынесли поле для видимости в обработчиках
        MembershipUserCollection myUsers = new MembershipUserCollection();
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Скрываем таблицу деталей
            tableDetails.Visible = false;
            lblUser.Visible = false;
        
            // Извлекаем всех пользователей
            myUsers = Membership.GetAllUsers();
        
            // Подключаем к сетке извлеченную информации и наполняем элемент
            UsersGridView.DataSource = myUsers;
            UsersGridView.DataBind();
        }
        
        // Обработчик щелчков на ссылки столбца отображения деталей
        protected void UsersGridView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (UsersGridView.SelectedIndex >= 0)
            {
                // Заполняем интерфейсные элементы отображаемой информацией,
                // адресуясь к конкретному пользователю по ключевому имени,
                // заданному параметром DataKeyNames="UserName" элемента GridView 
                MembershipUser currentUser = 
                    myUsers[(string)UsersGridView.SelectedValue];
                lblUserName.Text = currentUser.UserName;
                txtEmail.Text = currentUser.Email;
                lblPasswordQuestion.Text = currentUser.PasswordQuestion;
                lblLastLoginDate.Text = currentUser.LastLoginDate.ToLongDateString();
                txtComment.Text = currentUser.Comment;
                checkIsApproved.Checked = currentUser.IsApproved;
                checkIsLockedOut.Checked = currentUser.IsLockedOut;
                
                // Включаем видимость таблицы деталей
                tableDetails.Visible = true;
                lblUser.Visible = true;
            }
        }
        
        protected void btnUpdate_Click(object sender, EventArgs e)
        {
            if (UsersGridView.SelectedIndex >= 0)
            {
                // Адресуемся к выделенному пользователю
                MembershipUser currentUser =
                    myUsers[(string)UsersGridView.SelectedValue];
                    
                // Избавляемся от возможных управляющих символов
                // в пользовательском вводе 
                currentUser.Email = Server.HtmlEncode(txtEmail.Text);
                currentUser.Comment = Server.HtmlEncode(txtComment.Text);
                    
                currentUser.IsApproved = checkIsApproved.Checked;
                
                // Сбрасываем в базу данных 
                Membership.UpdateUser(currentUser);
                
                // Обновить содержимое GridView из БД, поскольку
                // этот обработчик выполнится после Page_Load
                myUsers = Membership.GetAllUsers();// Извлекаем пользователей
                UsersGridView.DataBind();
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <h2>
                    <asp:Label ID="lblUsersList" runat="server" Font-Bold="true" 
                        Text="Список зарегистрированных пользователей"></asp:Label>
                </h2>
                <asp:GridView ID="UsersGridView" runat="server" 
                    DataKeyNames="UserName" 
                    AutoGenerateColumns="False" 
                    OnSelectedIndexChanged="UsersGridView_SelectedIndexChanged">
                    <Columns>
                        <asp:BoundField DataField="UserName" HeaderText="Имя" />
                        <asp:BoundField DataField="Email" HeaderText="email" />
                        <asp:BoundField DataField="CreationDate" HeaderText="Создан" />
                        <asp:CommandField ShowSelectButton="True" HeaderText="Детали" 
                            SelectText="Показать" />
                    </Columns>
                </asp:GridView>
                <h2>
                    <asp:Label ID="lblUser" runat="server" 
                        Text="Подробности для выделенного пользователя"
                        Visible="False" Font-Bold="true"></asp:Label>
                </h2>            
                <table id="tableDetails" bordercolor="#0000FF" 
                    runat="server">
                    <tr>
                        <td width="50%">
                            Пользователь:</td>
                        <td>
                            <asp:Label ID="lblUserName" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font>email:</font></td>
                        <td>
                            <asp:TextBox ID="txtEmail" runat="server">
                            </asp:TextBox>
                            <asp:RequiredFieldValidator
                                ID="RequiredFieldValidator1" runat="server" 
                                ControlToValidate="txtEmail" Display="Dynamic">*
                            </asp:RequiredFieldValidator>
                            <asp:RegularExpressionValidator ID="RegularExpressionValidator1" 
                                runat="server" ControlToValidate="txtEmail"
                                Display="Dynamic" 
                                ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">*
                            </asp:RegularExpressionValidator></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Контрольный вопрос:</td>
                        <td>
                            <asp:Label ID="lblPasswordQuestion" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Дата последней регистрации:</td>
                        <td>
                            <asp:Label ID="lblLastLoginDate" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font>Комментарии:</font></td>
                        <td>
                            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" 
                               ></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font>Проверенный:</font></td>
                        <td>
                            <asp:CheckBox ID="checkIsApproved" runat="server" /></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Заблокирован:</td>
                        <td>
                            <asp:CheckBox ID="checkIsLockedOut" runat="server" /></td>
                    </tr>
                    <tr>
                        <td colspan="2" align="center">
                            <asp:Button ID="btnUpdate" runat="server" Text="Обновить" 
                                OnClick="btnUpdate_Click" /></td>
                    </tr>
                </table>
            </div>
        </form>
    </body>
    </html>

    Элементы управления вкладки Login

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

    Это следующие компоненты:

  • Login - предоставляет два поля для ввода имени пользователя и пароля, а также кнопку для отправки на проверку удостоверения пользователя. По умолчанию проверка выполняется через API Membership
  • LoginView - позволяет отображать разные наборы элементов управления для разных категорий пользователей
  • PasswordRecovery - предоставляет необходимый интерфейс для запроса контрольного ответа и обеспечивает повторную отправку пользователю утраченного пароля через электронную почту
  • LoginStatus - проверяет состояние аутентификации текущего сеанса и предоставляет страницу регистрации для неаутентифицированных пользователей
  • LoginName - элемент управления, который в заданном формате отображает имя только того пользователя, который успешно прошел регистрацию
  • CreateUserWizard - предоставляет многошаговый пользовательский интерфейс для ввода имени, пароля, контрольных вопроса и ответа, адреса элекстронной почты, необходимых при регистрации на сайте
  • ChangePassword - предоставляет пользовательский интерфейс для смены пароля
  • Все эти элементы управления по умолчанию настроены на работу с API Membership.

    Упражнение 9. Административная страница регистрации на базе элемента управления Login

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

    Пока не закончена...
    Страницы:

    Файлы к лабораторной работе Вы можете скачать здесь.

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

    Основные задачи по защите сайта

    Основные задачи по защите сайта следующие:

  • Аутентификация - опознавание источника сообщений по схеме "свой-чужой" с целью установить, является ли пользователь зарегистрированным на сайте или ананимным. Примером может служить проверка пропуска при входе в организацию
  • Авторизация - определение уровня полномочий по допуску к важной информации и выполнению над ней определенных действий. Примером может быть допуск к определенным производствам или документации на предприятии
  • Конфединциальность - изоляция данных одного пользователя от любого вида доступа со стороны других пользователей. Обеспечивается с помощью шифрования данных в каналах связи и в местах постоянного или временного хранения
  • Целостность - защита данных от изменений в результате неавторизованного вмешательства
  • Аутентификация нового пользователя начинается с его регистрации. Пользователю предлагается страница регистрации с текстовыми полями, через которую у него запрашиваются некоторые идентификационные данные. По этим данным создается и сохраняется учетная запись, которая для узнаваемости пользователя на сайте является некоторым маркером или удостоверением. Зарегистрировавшемуся пользователю может быть отослан некоторый ключ, который в дальнейшем будет служить дополнительным условием при доступе к сайту.

    Теперь при запросе ограниченного ресурса пользователь предъявит свое удостоверение, которое система верифицирует и примет решение: если удостоверение пользователя подтверждается, ему предоставляется доступ к Web-ресурсу, иначе будет предложена регистрация. Процесс аутентификации может выглядеть примерно так

    В приложениях ASP.NET аутентификация может быть реализована одним из следующих способов:

  • Аутентификация с помощью форм - поддерживается классом System.Web.Security.FormsAuthenticationModule
  • Аутентификация Windows - поддерживается классом System.Web.Security.WindowsAuthenticationModule
  • Аутентификация с помощью паспортов - поддерживается классом System.Web.Security.PassportAuthenticationModule
  • Специализированная аутентификация - поддерживается внебиблиотечными классами сторонних производителей или самодельным классом
  • Системы коммуникации шифруются с помощью системы SSL (Secure Sockets Layer - уровень защищенных сокетов). Поскольку система SSL является службой IIS (Internet Information Services - службы информационной поддержки Internet) и работает под HTTP, то специально коммуникации нам шифровать не нужно - все пересылки шифрует сама служба. Единственное отличие - адрес запроса, защищенный SSL, начинается не с http://, а с https://. И трафик SSL проходит не через порт 80 для обычных запросов, а через порт 443.

    Сертификаты

    Обычно сервер, которому клиент собирается пересылать важные данные, маркируется сертификатом. Сертификат - это некоторый паспорт сервера, подтверждающий тот факт, что ему можно доверять. Организация, которой принадлежит сервер, приобретает сертификат у известного центра сертификации CA (Certificate Authority) и инсталирует его на своем Web-сервере.

    Наиболее популярными центрами сертификации являются следующие сайты:

  • Thawte - http://www.thawte.com
  • VeriSign - http://www.verisign.com
  • Инсталированный на сервере сертификат содержит следующую информацию:

  • Серийный номер сертификата
  • Имя, название организации и адрес держателя сертификата
  • Даты проверки сертификата
  • Открытый ключ, который будет рассылаться клиентам для шифрования симметричного ключа сеанса
  • Клиент (броузер) доверяет CA и поэтому готов доверять подписанной CA информации. В свою очередь, CA сохраняет информацию о каждом зарегистрированном пользователе. Начало работы по защищенному каналу связи начинается так:

  • Броузер посылает запрос на соединение с сервером
  • В ответ сервер присылает свой сертификат
  • Броузер запрашивает из центра сертификации оригинал поступившего сертификата и сравнивает его с присланным сервером
  • Если информация в сертификатах совпадает, броузер считает адресуемый сервер заслуживающим доверия и решается на соединение. Он генерирует случайным образом симметричный ключ шифрования подходящей для него длины, шифрует его присланным в сертификате открытым ключем и отсылает серверу
  • Сервер получает от броузера зашифрованный ключ сеанса и расшифровывает его своим закрытым ключем. После этого у сервера и броузера есть одинаковый секретный ключ, с помощью которого они готовы применять симметричное шифрование к пересылаемой друг другу информации на протяжении всего текущего сеанса
  • Аутентификация форм

    Аутентификация (распознавание) на основе форм реализуется в пространстве имен System.Web.Security классами:

  • FormsAuthenticationModule
  • FormsAuthentication
  • FormsAuthenticationEventArgs
  • FormsAuthenticationTicket
  • FormsIdentity
  • Выбор этого режима устанавливается только в одном единственном месте - конфигурационном файле web.config корневого каталога сайта с помощью элемента

    <authentication mode="Forms" />

    Аутентификацию форм можно выполнять и с помощью API-интерфейсов Membership и Roles (об этом позже).

    Аутентификация форм основана на удостоверениях (мандатах), в которых хранится информация о зарегистрированном пользователе. Мандаты хранятся на сервере в базе данных зашифрованными и пересылаются при каждом запросе в cookie-наборах. ASP.NET при каждом запросе обрабатывает cookie-набор и задает по нему уровень безопасности запроса.

    Для реализации аутентификации форм нужно выполнить следующее:

  • Настроить режим аутентификации форм в файле web.config
  • Настроить службу IIS для разрешения анонимного доступа к виртуальному каталогу
  • Настроить ASP.NET для ограничения доступа к защищенным каталогам
  • Создать страницу регистрации
  • Настройка режима аутентификации форм

    В файле web.config виртуального корневого каталога сайта необходимо определить нужные параметры аутентификации, как свойства класса System.Web.Configuration.FormsAuthenticationConfiguration, из следующего набора:

    Опции аутентификации форм в файле web.config
    Опция Значение по умолчанию Описание
    Name .ASPXAUTH Имя cookie-набора HTTP для использования в аутентификации. При работе на одном сервере нескольких приложений cookie-набору безопасности каждого приложения нужно задать уникальное имя для гарантированной их изоляции
    LoginUrl login.aspx Определяет страницу регистрации пользователя при запросе защищенной страницы (login - вход в систему)
    Timeout 30 Время жизни cookie-набора безопасности в минутах. Если установлен режим скользящего устаревания и период между двумя соседними запросами не превысит этого таймаута, то ASP.NET обновит метку времени в cookie-наборе. Но если простой превысит установленный таймаут, будет затребована новая регистрация пользователя
    SlidingExpiration false Включает режим скользящего устаревания cookie-набора безопасности. В этом режиме время устаревания при каждом запросе будет сбрасываться, если очередной простой не превысит указанное значение Timeout
    Cookieless UseDeviceProfile Указывает способ пересылки удостоверения пользователя
    Protection All

    Задает уровень безопасности cookie-наборов аутентификации:

  • None - не шифровать и не подписывать
  • Encryption - только шифровать
  • Validation - только подписывать
  • All - шифровать и подписывать
  • RequireSSL false При включении требует передачи через SSL. Если SSL не активирован на сервере, то аутентификация форм работать не будет
    EnableCrossAppRedirects false Разрешает переадресацию между приложениями при использовании аутентификации форм. Оба приложения должны опираться на одно и то же хранилище удостоверений
    DefaultUrl default.aspx Указывает страницу, на которую направляется пользователь после успешной регистрации, если он напрямую запросил страницу регистрации, указанную в LoginUrl
    Domain пусто Может указывать хост, в рамках которого любое приложение может использовать одни и те же cookie-наборы
    Path / Путь для cookie-наборов. Рекомендуется по умолчанию

    Перечисленные свойства устанавливаются как атрибуты дочернего дескриптора <forms> секции <authentication mode= "Forms" />. Вот пример использования полного набора этих атрибутов в файле web.config

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName"
                    loginUrl="MyLogin.aspx"
                    timeout="20"
                    slidingExpiration="true"
                    cookieless="AutoDetect"
                    protection="All"
                    requireSSL="false"
                    enableCrossAppRedirects="false"
                    defaultUrl="MyDefault.aspx"
                    domain=""
                    path="/"
                /> 
            </authentication>
        </system.web>
    </configuration>

    Хранение удостоверений в конфигурационном файле

    Хоть это и не самый лучший способ хранения удостоверений пользователей, зато самый простой. Вообще, удостоверения можно хранить где угодно, только нужно предусмотреть соответствующий код их сравнения с введенными на странице регистрации данными. Если удостоверения хранятся в файле web.config, то их нужно помещать в дочерний для <forms> дескриптор <credentials>. Вот пример для трех пользователей

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName"
                    loginUrl="MyLogin.aspx"
                    timeout="20"
                    slidingExpiration="true"
                    cookieless="AutoDetect"
                    protection="All"
                    requireSSL="false"
                    enableCrossAppRedirects="false"
                    defaultUrl="MyDefault.aspx"
                    domain=""
                    path="/"
                >
                    <credentials passwordFormat="Clear">
                        <user name="Admin" password="Root" />
                        <user name="Petja" password="Xa-xa" />
                        <user name="Vasja" password="Xo-xo" />
                    </credentials>
                </forms>
            </authentication>
        </system.web>
    </configuration>

    Упражнение 1. Управление доступом анонимных пользователей

    Рассмотрим пример ограничения доступа к страницам сайта с помощью аутентификации форм и хранением удостоверений в конфигурационном файле. Пользователей в конфигурационный файл можно добавлять как декларативно, непосредственно редактируя секцию <credentials>, так и программно.

  • Создайте пустой проект Web-приложения командой File/New/Web Site с именем Login
  • Добавьте к приложению корневой конфигурационный файл Web.config, выполнив команду Website/Add New Item
  • Заполните конфигурационный файл кодом настройки аутентификации форм и декларативным созданием пользователей
  • <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName" 
                    loginUrl="MyLogin.aspx" 
                    defaultUrl="MyDefault.aspx"
                    protection="All" 
                    timeout="20" 
                    path="/" 
                    requireSSL="false"
                    slidingExpiration="true" 
                    cookieless="AutoDetect" 
                    domain=""
                    enableCrossAppRedirects="false">
                    <credentials passwordFormat="Clear">
                        <user name="Admin" password="Root" />
                        <user name="Петя" password="Уфamp;Уф" />
                        <user name="Вася" password='!$-*?"' />
                    </credentials>
                </forms>
            </authentication>
        </system.web>
    </configuration>

    Значение атрибута passwordFormat="Clear" обеспечивает открытое хранение пользователей в конфигурационном файле без всякого шифрования. Этот параметр применяется в надежде на то, что система автоматически ограничивает доступ конечных пользователей к конфигурационным файлам и что эти секретные данные никто не увидит. Но администратор сайта имеет доступ к конфигурационным файлам сайта, да и продвинутые злоумышленники тоже могут добраться до этой информации, обойдя защиту системы. Поэтому существуют встроенные алгоритмы хеширования ( MD5, SHA1 ), использование которых мы рассмотрим чуть позже.

    Программное добавление пользователей в конфигурационный файл

  • Добавьте к проекту командой Website/Add New Item страницу без отделенного кода с именем LoginAddUsers.aspx по следующему шаблону
  • Заполните страницу LoginAddUsers.aspx следующим кодом
  • <%@ Page Language="C#" %>
    <%@ Import Namespace="System.Web.Configuration" %>
        
    <script runat="server">
        
        protected void btnAddUsers_Click(object sender, EventArgs e)
        {
            // Учетные записи для новых аутентифицированных пользователей
            string[,] authUsers = {
                                    {"user1", "password1"},
                                    {"user2", "password2"},
                                    {"user3", "password3"}
                                  };
            
            // Загрузить содержимое корневого Web.config
            Configuration myConfig = System.Web.Configuration.
                WebConfigurationManager.OpenWebConfiguration("~/");
            
            // Найти содержимое секции system.web
            ConfigurationSectionGroup systemWeb = myConfig.SectionGroups["system.web"];
            
            // Найти содержимое секции authentication 
            System.Web.Configuration.AuthenticationSection authSec =
                (System.Web.Configuration.AuthenticationSection)
                systemWeb.Sections["authentication"];
            
            // Установить параметр passwordFormat (Clear, MD5, SHA1) 
            authSec.Forms.Credentials.PasswordFormat = System.Web.Configuration.FormsAuthPasswordFormat.Clear;
            
            // Добавить новых пользователей
            for (int i = 0; i < authUsers.Length / 2; i++)
                authSec.Forms.Credentials.Users.Add(new System.Web.Configuration.FormsAuthenticationUser(
                    authUsers[i, 0], authUsers[i, 1]));
            
            // Обновить файл Web.config 
            myConfig.Save();
        
            // Отсылаем сообщение 
            lblUsersResult.Text = "Новые пользователи добавлены!!!";
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div style="text-align: center">
        <h1>Административная страница добавления учетных записей в Web.config</h1>
            <p>
                <asp:Button ID="btnAddUsers" runat="server" Text="Добавить" OnClick="btnAddUsers_Click" /></p>
            <p>
                <asp:Label ID="lblUsersResult" runat="server"></asp:Label></p>
        </div>
        </form>
    </body>
    </html>

    Обратите внимание! Чтобы не использовать полные имена классов вместе с их пространствами имен:

    В страницах с отделенным кодом используется инструкция using

    В страницах с совмещенным кодом используется директива

    <%@ Import Namespace="System.Web.Configuration" %>

  • В режиме редактирования страницы LoginAddUsers.aspx выполните команду File/View in Browser
  • Откройте файл Web.config и убедитесь, что страница LoginAddUsers.aspx при выполнении программно добавила новых пользователей, а также скорректировала имена старых пользователей путем приведения символов к нижнему регистру. Но это несущественно, поскольку ASP.NET при сравнении имен регистр символов не учитывает
  • <credentials passwordFormat="Clear">
         <user name="admin" password="Root" />
         <user name="петя" password="Уфamp;Уф" />
         <user name="вася" password="!$-*?quot;" />
         <user name="user1" password="password1" />
         <user name="user2" password="password2" />
         <user name="user3" password="password3" />
        </credentials>
  • Добавьте к проекту две страницы без отделенного кода с именами Default.aspx и MyDefault.aspx со следующим кодом
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void SignOutAction_Click(object sender, EventArgs e)
        {
            // Уничтожить cookie-набор 
            FormsAuthentication.SignOut();
            // Направить на страницу регистрации
            //FormsAuthentication.RedirectToLoginPage();
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <h1>
                Страница с ограниченным доступом Default.aspx</h1>
            <p>
                <asp:Button ID="SignOutAction" runat="server" 
                Text="Отменить регистрацию" OnClick="SignOutAction_Click" />
                <asp:HyperLink ID="HyperLink1" runat="server" 
                NavigateUrl="~/MyDefault.aspx">На страницу MyDefault.aspx...
                </asp:HyperLink></p>
        </div>
        </form>
    </body>
    </html>
    <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void SignOutAction_Click(object sender, EventArgs e)
        {
            // Уничтожить cookie-набор 
            FormsAuthentication.SignOut();
            // Направить на страницу регистрации
            //FormsAuthentication.RedirectToLoginPage();
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <h1>
                    Страница с ограниченным доступом MyDefault.aspx</h1>
                <p>
                    <asp:Button ID="SignOutAction" runat="server" 
                    OnClick="SignOutAction_Click" Text="Отменить регистрацию" />
                    <asp:HyperLink ID="HyperLink1" runat="server" 
                    NavigateUrl="~/Default.aspx">На страницу Default.aspx...
                    </asp:HyperLink></p>
            </div>
        </form>
    </body>
    </html>
  • В проводнике решений вызовите контекстное меню для страницы Default.aspx или MyDefault.aspx и выполните команду View in Browser. Убедитесь, что пока ASP.NET разрешает пользоваться этими страницами без регистрации, т.е. любым анонимным пользователям
  • Добавьте в конфигурационный файл блок авторизации, запрещающий доступ всем анонимным пользователям и требующий обязательной регистрации
  • Окончательный вид файла Web.config должен стать таким

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName" 
                    loginUrl="MyLogin.aspx" 
                    defaultUrl="MyDefault.aspx"
                    protection="All" 
                    timeout="20" 
                    path="/" 
                    requireSSL="false"
                    slidingExpiration="true" 
                    cookieless="AutoDetect" 
                    domain=""
                    enableCrossAppRedirects="false">
                    <credentials passwordFormat="Clear">
                        <user name="admin" password="Root" />
                        <user name="петя" password="Уфamp;Уф" />
                        <user name="вася" password="!$-*?quot;" />
                        <user name="user1" password="password1" />
                        <user name="user2" password="password2" />
                        <user name="user3" password="password3" />
                    </credentials>
                </forms>
            </authentication>
            <authorization>
                <deny users="?"/>
            </authorization>
        </system.web>
    </configuration>
  • В проводнике решений вновь вызовите контекстное меню для страницы Default.aspx или MyDefault.aspx и выполните команду View in Browser. Убедитесь, что ASP.NET теперь не разрешает пользоваться этими страницами анонимно и пытается адресоваться к странице регистрации, назначенной нами в конфигурационном файле параметром loginUrl="MyLogin.aspx"
  • Создание страницы регистрации

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

    Страница регистрации должна включать в себя поля ввода для получения от пользователя данных удостоверения. Для проверки достоверности пользовательских данных необходимо использовать валидаторы, генерирующие нужный JavaScript -код на клиенте, а также выполняющие проверку на сервере.

  • Создайте заготовку новой страницы с именем MyLogin.aspx без отделенного кода
  • Заполните страницу элементами управления и настройте их согласно таблице
  • Таблица свойств элементов страницы регистрации MyLogin.aspx
    Элемент Свойство Значение Пояснение
    Panel ID MainPanel Родительская панель для оформления рамкой
      BorderColor Silver Цвет рамки
      BorderStyle Ridge Тип рамки
    TextBox ID UsernameText Текстовое поле ввода имени пользователя
    RequiredFieldValidator ID UsernameRequiredValidator Проверка достоверности, чтобы привязанное поле не было пустым
      ControlToValidate UsernameText Привязка к контролируемому элементу ввода
      Display Dynamic Место под элемент выделять только при визуализации
      ErrorMessage Не заполнено поле "Имя пользователя" Отображать в элементе ValidationSummary при ошибке
      ToolTip Пустое поле ввода Всплывающая подсказка для визуализированного валидатора
      Text * Надпись на визуализированном валидаторе
    RegularExpressionValidator ID UsernameValidator Валидатор регулярных выражений. Следит за вводом только допустимых символов
      ControlToValidate UsernameText Привязка к контролируемому элементу ввода
      Display Dynamic Место под элемент выделять только при визуализации
      ErrorMessage Неверное имя пользователя Отображать в элементе ValidationSummary при ошибке
      ToolTip Допустимы буквы, цифры, пробелы и подчеркивания Всплывающая подсказка для визуализированного валидатора
      ValidationExpression [а-яА-Я\w| ]*

    Регулярное выражение:

  • [] - диапазон пропускаемых символов
  • []* - символы диапазона могут встречаться в контролируемом вводе сколько угодно раз
  • а-яА-Я - разрешать все буквы русского алфавита
  • \w - разрешать все латинские буквы, цифры и знаки подчеркивания. Это управляющий символ регулярного выражения
  • | - знак ИЛИ, после которого стоит пробел, означает, что разрешать и пробелы
  • Таким образом: действительными считаются все русские буквы обоих регистров, все латинские буквы обоих регистров, цифры и знаки подчеркивания, а также пробелы. И все это в любом количестве повторений

      Text * Надпись на визуализированном валидаторе
    TextBox ID PasswordText Текстовое поле ввода пароля
      TextMode Password Ввод отображается звездочками
    RequiredFieldValidator ID PwdRequiredValidator Проверка достоверности, чтобы привязанное поле не было пустым
      ControlToValidate PasswordText Привязка к контролируемому элементу ввода
      Display Dynamic Место под элемент выделять только при визуализации
      ErrorMessage Не заполнено поле "Пароль" Отображать в элементе ValidationSummary при ошибке
      ToolTip Пустое поле ввода Всплывающая подсказка для визуализированного валидатора
      Text * Надпись на визуализированном валидаторе
    RegularExpressionValidator ID PwdValidator Валидатор регулярных выражений. Следит за вводом только допустимых символов
      ControlToValidate PasswordText Привязка к контролируемому элементу ввода
      Display Dynamic Место под элемент выделять только при визуализации
      ErrorMessage Неверный пароль Отображать в элементе ValidationSummary при ошибке
      ToolTip Используются недопустимые символы Всплывающая подсказка для визуализированного валидатора
      ValidationExpression [а-яА-Я\w| !"$amp;/()=\-?\*]* Регулярное выражение:
  • [] - диапазон пропускаемых символов
  • []* - символы диапазона могут встречаться в контролируемом вводе сколько угодно раз
  • а-яА-Я - разрешать все буквы русского алфавита
  • \w - разрешать все латинские буквы, цифры и знаки подчеркивания. Это управляющий символ регулярного выражения
  • | - знак ИЛИ, после которого стоит пробел и ряд других символов, разрешенных для ввода
  • Символы, которые могут интерпретироваться как служебные, экранируются обратной косой чертой. Интерпретируемый HTML-символ амперсанта заменяется неинтерпретируемым аналогом amp;

      Text * Надпись на визуализированном валидаторе
    Button ID Button1 Кнопка Submit
      Text Отправить Надпись
    Label ID lblResult Метка сообщения для заполнения в коде
    ValidationSummary ID ValidationSummary1 Элемент отображения сообщений об ошибках, обнаруженных валидаторами

    Интерфейсная часть страницы MyLogin.aspx в режиме проектирования будет такой

    Код страницы MyLogin.aspx будет таким

    <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        protected void LoginAction_Click(object sender, EventArgs e)
        {
            this.Validate();// Исполнить валидаторы на сервере
            if (!this.IsValid)// Оценить флаг достоверности
                return;// Отправить назад как есть
            
            // Извлечь из web.config и сравнить с введенным 
            if (FormsAuthentication.Authenticate(UsernameText.Text, PasswordText.Text))
            {
                // Создать временный cookie-набор (второй параметр false),
                // записать в него  метку аутентификации и перенаправить
                // на исходную запрошенную страницу или MyDefault.aspx
                FormsAuthentication.RedirectFromLoginPage(UsernameText.Text, false);
            }
            else
            {
                HtmlGenericControl message = new HtmlGenericControl();
                message.InnerHtml = "<h2 style='color: Red'>Неверное имя или пароль!</h2>";
                form1.Controls.Add(message);
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h1>Сегодня <% this.Response.Write(DateTime.Now.ToShortDateString()); %> г.</h1>
                <h2>
                    Введите свои имя и пароль</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table cellpadding="5" style="width: 100%">
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 167px">
                                Имяnbsp;пользователя:</td>
                            <td>
                                nbsp;<asp:TextBox ID="UsernameText" runat="server" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="UsernameRequiredValidator" 
                                    runat="server" ControlToValidate="UsernameText"
                                    Display="Dynamic" 
                                    ErrorMessage='Не заполнено поле "Имя пользователя"' 
                                    ToolTip="Пустое поле ввода">*
                                </asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="UsernameValidator" 
                                    runat="server" ControlToValidate="UsernameText"
                                    Display="Dynamic" ErrorMessage="Неверное имя пользователя" 
                                    ToolTip="Допустимы буквы, цифры, пробелы и подчеркивания"
                                    ValidationExpression="[а-яА-Я\w| ]*">*
                                </asp:RegularExpressionValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 167px">
                                nbsp;Пароль:</td>
                            <td>
                                nbsp;<asp:TextBox ID="PasswordText" runat="server" 
                                    TextMode="Password" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="PwdRequiredValidator" 
                                    runat="server" ControlToValidate="PasswordText"
                                    Display="Dynamic" ErrorMessage='Не заполнено поле "Пароль"' 
                                    ToolTip="Пустое поле ввода">*
                                </asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="PwdValidator" runat="server" 
                                    ControlToValidate="PasswordText"
                                    Display="Dynamic" ErrorMessage="Неверный пароль" 
                                    ToolTip="Используются недопустимые символы"
                                    ValidationExpression='[а-яА-Я\w| !"$amp;/()=\-?\*]*'>*
                                </asp:RegularExpressionValidator>
                            </td>
                        </tr>
                    </table>
                    <asp:Button ID="Button1" runat="server" Text="Отправить" 
                        OnClick="LoginAction_Click" />
                </asp:Panel>
                <asp:Label ID="lblResult" runat="server" />
            </div>
            <asp:ValidationSummary ID="ValidationSummary1" runat="server" />
        </form>
    </body>
    </html>
  • В проводнике решений вызовите контекстное меню для страницы Default.aspx или MyDefault.aspx и выполните команду View in Browser
  • При такой инфраструктуре аутентификации любой пользователь, открывший сеанс с приложением, какую бы страницу он не запросил, будет направляться на страницу регистрации до тех пор, пока не введет свои данные, хранящиеся в удостоверении конфигурационного файла. После совпадения удостоверения будет создан cookie -набор аутентификации со скользящим временем жизни, в котором в зашифрованном виде будет записан мандат, и этот мандат будет действовать на протяжении всего сеанса, пока не истечет допустимое временя простоя сеанса или пока cookie-набор аутентификации не будет нами уничтожен.

    Упражнение 2. Хеширование паролей в конфигурационном файле

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

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

    Параметр passwordFormat может принимать одно из трех следующих значений:

  • Clear - пароль не хеширован и пользовательский пароль нужно сравнивать без предварительного хеширования
  • MD5 - пароль хеширован алгоритмом MD5 и перед сравнением присланный пользовательзователем пароль хешировать тем же алгоритмом
  • SHA1 - для хеширования применять алгоритм SHA1
  • Для предварительного хеширования паролей в Web.config применяется метод FormsAuthentication.HashPasswordForStoringInConfigFile().

  • Добавьте к проекту страницу HashPasswordPage.aspx с встроенным скриптом и наполните ее следующим кодом
  • <%@ Page Language="C#" %>
        
    <%@ Import Namespace="System.Web.Configuration" %>
        
    <script runat="server">
        
        protected void btnHashedPwd_Click(object sender, EventArgs e)
        {
            // Загрузить содержимое корневого Web.config
            Configuration myConfig = System.Web.Configuration.
                WebConfigurationManager.OpenWebConfiguration("~/");
            
            // Найти содержимое секции system.web
            ConfigurationSectionGroup systemWeb = myConfig.SectionGroups["system.web"];
            
            // Найти содержимое секции authentication 
            AuthenticationSection authSec =
                (AuthenticationSection)
                systemWeb.Sections["authentication"];
            
            // Установить параметр passwordFormat, сообщающий, что было хеширование по MD5
            authSec.Forms.Credentials.PasswordFormat = FormsAuthPasswordFormat.MD5;
            
            // Извлекаем из коллекции Users старые имена и пароли 
            int count = authSec.Forms.Credentials.Users.Count;
            string[] name = new string[count];
            string[] clearTextPwd = new string[count];
            int i = 0;
            foreach (FormsAuthenticationUser user in authSec.Forms.Credentials.Users)
            {
                name[i] = user.Name;
                clearTextPwd[i] = user.Password;
                i++;
            }
            
            // Очищаем коллекцию Users 
            authSec.Forms.Credentials.Users.Clear();
            
            // Добавляем старые имена и новые хешированные пароли
            for (i = 0; i < count; i++)
            {
                // Хешируем исходный пароль
                string hashedPwd = FormsAuthentication.
                    HashPasswordForStoringInConfigFile(clearTextPwd[i], "MD5");
                // Добавляем в коллекцию Users
                authSec.Forms.Credentials.Users.Add(
                    new FormsAuthenticationUser(name[i], hashedPwd));
            }
            
            // Обновляем файл Web.config 
            myConfig.Save();
            
            // Отсылаем сообщение 
            lblHashResult.Text = "Хеширование завершено.<br />"
                + "Повторно не выполнять - будет хеш на хеш!!!";
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h1>
                    Административная страница хеширования паролей в Web.config</h1>
                <p>
                    <asp:Button ID="btnHashedPwd" runat="server" 
                                OnClick="btnHashedPwd_Click" Text="Хешировать" /></p>
                <p>
                    <asp:Label ID="lblHashResult" runat="server"></asp:Label></p>
            </div>
        </form>
    </body>
    </html>
  • Выполните страницу HashPasswordPage.aspx, но только один раз, чтобы повторно не хешировать уже хешированные пароли
  • В результате мы получим вариант конфигурационного файла с хешированными паролями

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.web>
            <compilation debug="true" />
            <authentication mode="Forms">
                <forms 
                    name="MyCookieName" 
                    loginUrl="MyLogin.aspx" 
                    defaultUrl="MyDefault.aspx"
                    protection="All"
                    timeout="20"
                    path="/" 
                    requireSSL="false"
                    slidingExpiration="true"
                    cookieless="AutoDetect"
                    domain=""
                    enableCrossAppRedirects="false"
                >
                    <credentials passwordFormat="MD5">
                        <user name="admin" password="FA03EB688AD8AA1DB593D33DABD89BAD" />
                        <user name="петя" password="B63974E3EE7B0920FE24DFB8E82FAE81" />
                        <user name="вася" password="44D4B2D42E830BE0DD43E6830D957C5D" />
                        <user name="user1" password="7C6A180B36896A0A8C02787EEAFB0E4C" />
                        <user name="user2" password="6CB75F652A9B52798EB6CF2201057C73" />
                        <user name="user3" password="819B0643D6B89DC9B579FDFC9094F28E" />
                    </credentials>
                </forms>
            </authentication>
            <authorization>
                <deny users="?"/>
            </authorization>
        </system.web>
    </configuration>
  • Откройте в редакторе оболочки страницу Default.aspx и командой File/View in Browser запустите ее на выполнение
  • Вначале будет предложена страница регистрации, поскольку конфигурация сайта настроена именно так.

  • Введите в странице регистрации MyLogin.aspx учетную запись user1 и password1
  • Исходные пароли, вводимые пользователями при регистрации, остались прежними. Но в конфигурационном файле они теперь храняться как хешированные по алгоритму MD5 и являются бесполезными для злоумышленников. Сама же система при аутентификации приводит исходные пароли перед сравнением к хешированному виду по тому же самому алгоритму. Если исходный и оригинальный пароли правильные, то их хеши будут совпадать.

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

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

    Упражнение 3. Закрепление за броузером постоянной аутентификации форм

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

    Постоянные cookie-набор можно создать

  • в странице регистрации при первой посещении пользователя установкой второго параметра в значение true (вместо false) в методе

    FormsAuthentication.RedirectFromLoginPage( строка_имя , true);

  • в любом другом месте открытого сеанса выполнением метода

    FormsAuthentication.SetAuthCookie( строка_имя , true);

  • в любом другом месте открытого сеанса выполнением кода

    HttpCookie authCookie = FormsAuthentication.GetAuthCookie( строка_имя , true);

    this.Response.Cookies.Add(authCookie);

  • Установленный таким способом cookie-набор будет существовать на компьютере пользователя "вечно", если только мы не удалим его методом

    FormsAuthentication.SignOut();

    Можно также изменить временной статус постоянного cookie-набора на любой странице. Покажем это

  • Добавьте к проекту страницу ModifyPersistentCookieAuth.aspx без файла отделенного кода и заполните ее так
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        Label message;
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Дескриптор центрирования
            HtmlGenericControl center = new HtmlGenericControl("center");
            form1.Controls.Add(center);
            
            // Текстовая метка с заголовком 
            Label label = new Label();
            center.Controls.Add(label);
            label.Text = "<h2>Закрепить аутентификацию за броузером</h2>";
            
            // Кнопки
            Button createPersistentCookie = new Button();
            center.Controls.Add(createPersistentCookie);
            createPersistentCookie.Text = "Создать бессрочный AuthCookie";
            createPersistentCookie.Click += new EventHandler(createPersistentCookie_Click);
            center.Controls.Add(new HtmlGenericControl("br"));
            
            Button createTemporaryCookie = new Button();
            center.Controls.Add(createTemporaryCookie);
            createTemporaryCookie.Text = "Создать срочный AuthCookie";
            createTemporaryCookie.Click += new EventHandler(createTemporaryCookie_Click);
            center.Controls.Add(new HtmlGenericControl("br"));
        
            Button deleteCookie = new Button();
            center.Controls.Add(deleteCookie);
            deleteCookie.Text = "Удалить AuthCookie";
            deleteCookie.Click += new EventHandler(deleteCookie_Click);
            center.Controls.Add(new HtmlGenericControl("br"));
            
            // Текстовая метка с сообщением
            message = new Label();
            center.Controls.Add(message);
            message.Text = String.Empty;
        }
        
        void createPersistentCookie_Click(object sender, EventArgs e)
        {
            // Создать бессрочный cookie
            HttpCookie authCookie = FormsAuthentication.GetAuthCookie("xx", true);
            
            // Отослать на броузер
            this.Response.Cookies.Add(authCookie);
            
            // Сообщить пользователю
            message.Text = "Бессрочный AuthCookie создан!";
        }
        
        void createTemporaryCookie_Click(object sender, EventArgs e)
        {
            // Создать вначале бессрочный cookie
            HttpCookie authCookie = FormsAuthentication.GetAuthCookie("xx", true);
            
            // Настроить его как временный сроком на 10 дней
            authCookie.Expires = DateTime.Now.AddDays(10);
        
            // Отослать на броузер
            this.Response.Cookies.Add(authCookie);
        
            // Сообщить пользователю
            message.Text = "Срочный AuthCookie создан!";
        }
        
        void deleteCookie_Click(object sender, EventArgs e)
        {
            // Удалить cookie-набор регистрации
            FormsAuthentication.SignOut();
        
            // Сообщить пользователю
            message.Text = "AuthCookie удален!";
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        </form>
    </body>
    </html>

    Интерфейс страницы в режиме выполнения будет таким

    При закреплении cookie-набора за броузером при соединении с сайтом страница регистрации пользователю предъявляться не будет, пока не закончится срок действия (если cookie-набор срочный).

    API Membership является готовым механизмом в ASP.NET, предназначенным для организации управления аутентификацией пользователей. Он базируется на аутентификации форм. Единственное отличие, что хранение пользователей будет осуществляться не в конфигурационном файле, а в базе данных. И для управления задействуются классы Membership.

    Чаще всего к корневому каталогу сайта допускаются все анонимные пользователи без регистрации, а защищенные ресурсы, требующие аутентификацию, размещаются в подкаталогах со своими файлами Web.config. Если кто-то запрашивает защищенный ресурс из подкаталога, система автоматически перенаправляет пользователя на страницу регистрации.

    Для использования API Membership нужно выполнитиь следующие шаги:

  • Сконфигурировать аутентификацию форм в корневом каталоге приложения и авторизацию в защищенных каталогах
  • Создать и настроить базу данных для хранения удостоверений пользователей
  • Занести в хранилище пользователей
  • Создать страницу регистрации
  • Упражнение 4. Создание хранилища удостоверений

    SQL Server 2005 создает базу данных и работает с ней как с отдельным файлом с расширением .mdf. По умолчанию файлы баз данных размещаются в каталоге

    C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data

    Мы сами можем вручную создать файл базы данных для хранения удостоверений и добавить в нее таблицы с необходимыми полями. Но это может несогласоваться с механизмом API Membership, поскольку он ожидает базу данных определенной структуры. К тому же нужно будет прописывать в конфигурационном файле строку соединений с этой базой и другие конфигурационные параметры, необходимые для работы API Membership. Более простой вариант предусмотрели разработчики Visual Studio 2005, включив поддержку Membership в утилиту Web Site Administration Tool ( WAT ). Теперь база данных нужной структуры для хранения удостоверений будет создана автоматически при первом запуске WAT, если в конфигурационном файле прописана секция аутентификации форм.

  • Откройте файл web.config и удалите из него секцию <credentials> вместе с внесенными ранее пользователями и временно закомментируйте секцию авторизации. При этом должна остаться секция аутентификации форм
  • В результате конфигурационный файл должен стать таким

    <?xml version="1.0" encoding="utf-8"?>
    <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
      <system.web>
        <compilation debug="true" />
        <authentication mode="Forms">
          <forms name="MyCookieName"
            loginUrl="MyLogin.aspx"
            defaultUrl="MyDefault.aspx"
            protection="All"
            timeout="20"
            path="/"
            requireSSL="false"
            slidingExpiration="true"
            cookieless="AutoDetect"
            domain=""
            enableCrossAppRedirects="false">
          </forms>
        </authentication>
        <!-- Временно закомментировали, чтобы не требовал регистрации
        <authorization>
          <deny users="?"/>
        </authorization>
        -->
      </system.web>
    </configuration>
  • Запустите утилиту WAT командой Website/ASP.NET Configuration и обратите внимание, как долго она запускается
  • Замечание. Эта утилита может не запускаться, если абсолютный путь к созданному проекту имеет пробелы или кириллицу. В этом случае переместите проект в другое место файловой системы компьютера.

  • В панели Solution Explorer щелкните на кнопке Refresh
  • Мы видим, что утилита WAT так долго запускалась потому, что добавила к проекту каталог App_Data (если до этого его еще не было) и создала в нем базу данных ASPNETDB.MDF, готовую для хранения удостоверений и согласованную с API Membership

  • В панели Solution Explorer двойным щелчком на файле ASPNETDB.MDF откройте соединение с этой базой данных и ознакомтесь в панели Server Explorer с ее структурой, типичной для SQL Server
  • Такой способ хранения удостоверений пользователей в локальной базе данных вместе с приложением очень удобен, поскольку позволяет перемещать сайт в любое место сервера или разворачивать на любом сервере без дополнительных настроек подключения SQL Server. Да и структуру базы данных утилита WAT создала такой, какая нужна системе API Membership. Это значит, что все классы Membership будут согласованы с хранилищем удостоверений.

    Утилита WAT автоматически создает хранилище удостоверений только в том случае, если обнаружит в корневом конфигурационном файле секцию

    <authentication mode="Forms">

    Если на момент запуска утилиты WAT файл конфигурации в корневом каталоге приложения отсутствует или в нем нет секции аутентификации на основе форм, то утилита базу данных не создает.

    Для того, чтобы проверить работоспособность созданной локальной базы данных, выполните следующее:

  • Запустите утилиту WAT командой Website/ASP.NET Configuration
  • На вкладке Home щелкните на гиперссылке Provider Configuration, чтобы перейти на вкладку Provider
  • На вкладке Provider щелкните на первой гиперссылке " Select a single provider for all site management data "
  • В отклике, сгенерированном административной страницей ManageConsolidatedProviders.aspx, щелкните на гиперссылке Test
  • В результате страница пришлет ответ, что созданная нами локальная база данных функционирует нормально.

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

    Добавление в хранилище БД новых пользователей через утилиту WAT

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

    Для создания новых аутентифицированных пользователей через утилиту WAT выполните следующее:

  • В панели Solution Explorer щелкните на пиктограмме ASP.NET Configuration, чтобы запустить утилиту WAT
  • Перейдите на вкладку Security и щелкните на гиперссылке Create user
  • Появится следующий отклик утилиты WAT

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

    В утилите за полем ввода пароля закреплен валидатор для контроля соблюдения требований к строгости пароля. По умолчанию настройка валидаторов пароля такая, что пароль должен иметь длину не менее 7 символов. Причем, как минимум один из символов должен быть неалфавитно-цифровым, например, символ . Это обеспечивает минимально необходимую строгость пароля. Позднее мы рассмотрим, как в конфигурационном файле можно менять принятые по умолчанию настройки Membership, которые, в том числе, будут влиять и на поведение WAT.

  • Заполните поля страницы для добавления в хранилище одного пользователя со следующими характеристиками
  • User Name: admin
    Password: RootRoot
    Confirm Password: RootRoot
    E-mail: Dialog-Service@List.ru
    Security Question: У Вас продается славянский шкаф?
    Security Answer: Шкаф уже продан, осталась только тумбочка!

    и щелкните на кнопке Create User

    После этого действия новый пользователь будет создан в базе данных.

  • В появившемся отклике щелкните на кнопке Continue, чтобы вернутся к интерфейсу добавления следующего пользователя, либо закройте WAT
  • Страница регистрации при использовании Membership

    На данном этапе для проверки аутентификации с использованием API Membership и хранилища БД мы воспользуемся страницей регистрации MyLogin.aspx. Ее нужно только немного модифицировать.

  • Модифицируйте страницу регистрации MyLogin.aspx, чтобы код стал таким
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        protected void LoginAction_Click(object sender, EventArgs e)
        {
            this.Validate();// Исполнить валидаторы на сервере
            if (!this.IsValid)// Оценить флаг достоверности
                return;// Отправить назад как есть
            
            // Извлечь из базы данных и сравнить с введенным 
            if (Membership.ValidateUser(UsernameText.Text, PasswordText.Text))
            {
                // Создать временный cookie-набор (второй параметр false),
                // записать в него  метку аутентификации и перенаправить
                // на исходную запрошенную страницу или defaultUrl="MyDefault.aspx"
                // с созданием coockie-набора только на период сеанса
                FormsAuthentication.RedirectFromLoginPage(UsernameText.Text, false);
            }
            else
            {
                HtmlGenericControl message = new HtmlGenericControl();
                message.InnerHtml = "<h2 style='color: Red'>Неверное имя или пароль!</h2>";
                form1.Controls.Add(message);
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h1>Сегодня <% this.Response.Write(DateTime.Now.ToShortDateString()); %> г.</h1>
                <h2>
                    Введите свои имя и пароль</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table cellpadding="5" style="width: 100%">
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 167px">
                                Имяnbsp;пользователя:</td>
                            <td>
                                nbsp;<asp:TextBox ID="UsernameText" runat="server" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="UsernameRequiredValidator" 
                                    runat="server" ControlToValidate="UsernameText"
                                    Display="Dynamic" 
                                    ErrorMessage='Не заполнено поле "Имя пользователя"' 
                                    ToolTip="Пустое поле ввода">*
                                </asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="UsernameValidator" 
                                    runat="server" ControlToValidate="UsernameText"
                                    Display="Dynamic" ErrorMessage="Неверное имя пользователя" 
                                    ToolTip="Допустимы буквы, цифры, пробелы и подчеркивания"
                                    ValidationExpression="[а-яА-Я\w| ]*">*
                                </asp:RegularExpressionValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 167px">
                                nbsp;Пароль:</td>
                            <td>
                                nbsp;<asp:TextBox ID="PasswordText" runat="server" 
                                    TextMode="Password" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="PwdRequiredValidator" 
                                    runat="server" ControlToValidate="PasswordText"
                                    Display="Dynamic" ErrorMessage='Не заполнено поле "Пароль"' 
                                    ToolTip="Пустое поле ввода">*
                                </asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="PwdValidator" runat="server" 
                                    ControlToValidate="PasswordText"
                                    Display="Dynamic" ErrorMessage="Неверный пароль" 
                                    ToolTip="Используются недопустимые символы"
                                    ValidationExpression='[а-яА-Я\w| !"$amp;/()=\-?\*]*'>*
                                </asp:RegularExpressionValidator>
                            </td>
                        </tr>
                    </table>
                    <asp:Button ID="Button1" runat="server" Text="Отправить" 
                        OnClick="LoginAction_Click" />
                </asp:Panel>
                <asp:Label ID="lblResult" runat="server" />
            </div>
            <asp:ValidationSummary ID="ValidationSummary1" runat="server" />
        </form>
    </body>
    </html>
  • Запустите приложение с любой страницы и введите учетную запись, сохраненную нами ранее в базе данных
  • User Name: admin
    Password: RootRoot
  • С помощью утилиты WAT добавьте в хранилище еще несколько пользователей и убедитесь, что аутентификация на основе Membership и хранилища удостоверений в БД работает
  • При добавлении новых пользователей следует иметь ввиду, что имя пользователя сохраняется в поле UserName реляционной таблицы aspnet_Users и является основой для формирования поля UserId как первичного ключа. Поэтому все пользователи должны иметь уникальные имена и регистр символов не имеет значения.

  • В панели Server Explorer установите соединение с базой данных ASPNETDB двойным щелчком указателя мыши на пиктограмме базы
  • Для таблиц aspnet_Users и aspnet_Membership выполните команду Show Table Data контекстного меню
  • После таких действий откроются таблицы aspnet_Users и aspnet_Membership, в которых можно найти информацию о созданных пользователях. Для того, чтобы увидеть структуру таблиц, следует выполнить для них команду Open Table Definition контекстного меню.

    Административные страницы управления аутентификацией с использованием API Membership

    Интерфейс API Membership включает в себя следующие классы пространства имен System.Web.Security:

  • Membership - статический класс, имеющий ряд полезных методов управления пользователями
  • MembershipCreateUserException - исключение, генерируемое ошибкой создания пользователя (например, с уже существующим именем)
  • MembershipPasswordException - исключение при невозможности извлечения пароля
  • MembershipUser - объект этого класса содержит всю информацию о пользователе и возвращается методом Membership.GetUser()
  • MembershipUserCollection - коллекция пользователей, извлекаемая методом Membership.GetAllUsers()
  • MembershipProvider - базовый класс для поставщиков данных
  • MembershipProviderCollection - коллекция доступных для приложения поставщиков на данной машине
  • SqlMembershipProvider - поставщик для работы с базами данных SQL Server
  • ActiveDirectoryMembershipProvider - поставщик для работы со службой Active Directory
  • ActiveDirectoryMembershipUser - класс поддержки пользователя, расширенный для работы со службой Active Directory
  • Используя API Membership, можно программно решать, например, следующие задачи:

  • Создание, редактирование и удаление пользователей
  • Извлечение списков пользователей со всей подробной информацией, занесенной при создании
  • Упражнение 5. Административная страница создания пользователей в БД

    Для создания и удаления пользователей следует применить перегруженные статические методы CreateUser() и DeleteUser() класса Membership. Нужно создать административные страницы, содержащие требуемые поля для ввода информации. Поля можно контролировать валидаторами, хотя это и необязательно, поскольку эти страницы административные, пользоваться такими страницами будут администраторы, которые будут строго соблюдать правила ввода.

    Метод CreateUser() имеет несколько перегрузок, из которых простейшая принимает только имя и пароль. В то же время более полные перегрузки требуют ввода контрольного вопроса и ответа на него. В самой полной перегрузке метод CreateUser() содержит экземпляр перечисления MembershipCreateStatus в качестве выходного параметра, позволяющий проанализировать причину ошибки в случае ее возникновения. По умолчанию требуется применять наиболее полную перегрузку метода CreateUser(), включающую контрольный вопрос и ответ, чтобы избежать выброса исключения MembershipCreateUserException.

    Удаление пользователя выполняется методом Membership.DeleteUser(string), которому передается в качестве параметра уникальное имя пользователя. Пользователь безвозвратно удаляется из всех таблиц вместе со всей связанной с ним информацией.

    В качестве примера создадим собственную страницу добавления, имеющую аналогичную функциональность с WAT, и отдельно страницу удаления пользователя в БД. Для простоты эти страницы мы переделаем из страницы регистрации MyLogin.aspx.

  • Скопируйте страницу MyLogin.aspx и присвойте копии имя AddUserMembership.aspx
  • Провайдер базы данных не добавляет новых пользователей, если пароль имеет длину менее 7 символов и если среди них нет хотя-бы одного неалфавитно-цифрового символа. Для проверки выполнения этого условия, а также для тренировки, закрепим за полем пароля кодируемый элемент проверки достоверности CustomValidator. С помощью этого элемента реализуем проверку достоверности на клиенте и на сервере.

    Неалфавитно-цифровые символы представим в виде ASCII -кодов согласно следующей таблице

    Набор неалфавитно-цифровых символов ASCII
      0 1 2 3 4 5 6 7 8 9
    0                   
    1                   
    2                   
    3      ! " # $ % '
    4( ) * + , . /      
    5                : ;
    6< = > ? @          
    7                   
    8                   
    9  [ \ ] ^ _        
    10                   
    11                   
    12      { | } ~      

    Строка ASCII -кодов неалфавитно-цифровых символов на JavaScript будет такой:

    var nonAlphanumericCharactersString = String.fromCharCode(33,34,35,36,37,38,39,40,41,42,43,44,45,46,                                 58,59,60,61,62,63,64,91,92,93,94,95,123,124,125,126);

  • Переделайте интерфейсную и скриптовую части страницы, чтобы она стала такой
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        // Блок на C# по умолчанию 
        protected void Page_Load(object sender, EventArgs e)
        {
            // Вынесли из интерфейсной части из-за большой ширины листинга
            passwordValidator.ErrorMessage = 
                "Пароль должен иметь длину не менее 7 символов"
                + " и содержать хотя бы один неалфавитно-цифровой символ";
        }
        
        protected void AddUserMembership_Click(object sender, EventArgs e)
        {
            this.Validate();// Исполнить валидаторы на сервере
            if (!this.IsValid)// Оценить флаг достоверности
                return;// Отправить назад как есть
        
            // Создаем экземпляр перечисления
            MembershipCreateStatus status;
        
            // Попытка добавить
            try
            {
                Membership.CreateUser(
                    username.Text,
                    password.Text,
                    email.Text,
                    question.Text,
                    answer.Text,
                    true,
                    out status
                    );
        
                lblResult.Text = String.Empty;
                switch (status)
                {
                    case MembershipCreateStatus.DuplicateUserName:
                        lblResult.Text = "Дублированное имя пользователя<br/>";
                        goto default;
                    case MembershipCreateStatus.ProviderError:
                        lblResult.Text = "Ошибка Поставщика<br/>";
                        goto default;
                    default: // Дальше лень анализировать
                        lblResult.Text += "Пользователь не создан";
                        lblResult.ForeColor = System.Drawing.Color.Red;
                        break;
                    case MembershipCreateStatus.Success:
                        // Выдаем успешную информацию
                        lblResult.Text = "Пользователь " + username.Text 
                            + " создан успешно!";
                        // Рекурсивно очищаем все текстовые поля ввода
                        EmptyTextBox(form1.Controls);
                        lblResult.Text += "<br/>Поля ввода очищены";
                        break;
                }
            }
            // Откат
            catch (MembershipCreateUserException ex)
            {
                lblResult.Text = "<h2 style='color: Red'>Ошибка создания пользователя</h2>";
                System.Diagnostics.Debug.WriteLine("Exception: "
                    + ex.Message);
            }
        }
        
        private void EmptyTextBox(ControlCollection controls)
        {
            foreach (Control ctrl in controls)
            {
                if (ctrl is TextBox)
                    ((TextBox)ctrl).Text = String.Empty;
        
                // Идем в глубину иерархической цепочки и ищем дочерние элементы
                if (ctrl.Controls != null)
                    EmptyTextBox(ctrl.Controls);
            }
        }
    </script>
        
    <script language="C#" runat="server">
    // Еще один блок с явным указанием C#, хотя можно атрибут языка и не указывать
        
        // Проверка допустимости пароля на сервере 
        // Обработчик события ServerValidate элемента CustomValidator
        protected void passwordValidator_ServerValidate(object source, ServerValidateEventArgs args)
        {
            string data = args.Value;
        
            // Установить начальное значение флага достоверности
            args.IsValid = false;
        
            // Проверить длину пароля 
            if (data.Length < 7)
                return;
        
            // Проверить наличие неалфавитноцифрового символа
            // Формируем массив неалфавитноцифровых символов по их кодам,
            // чтобы не связываться с интерпретируемыми символами
            int[] nonAlphanumericCharactersCode =
                {33,34,35,36,37,38,39,40,41,42,43,44,45,46,
                 58,59,60,61,62,63,64,91,92,93,94,95,123,124,125,126};
            
            // Перебираем символы пользовательского ввода
            foreach (char ch in data)
            {
                for (int i = 0; i < nonAlphanumericCharactersCode.Length; i++)
                    if (ch == Convert.ToChar(nonAlphanumericCharactersCode[i]))
                    {
                        // Пароль допустим 
                        args.IsValid = true;
                        return;
                    }
            }
        }
    </script>
        
    <script language="javascript" type="text/javascript">
        // Проверка допустимости пароля на клиенте. Работает при включенном
        // по умолчанию свойстве EnableClientScript элемента CustomValidator
            
        function ValidatePassword(source, args)
        {
            // Расщепить строку на массив символов с пустым разделителем
            var data = args.Value.split("");
            
            // Установить начальное значение флага достоверности
            args.IsValid = false;
            
            // Проверить длину пароля 
            if(data.length < 7)
                return;
                
            // Проверить наличие неалфавитноцифрового символа
            // Формируем строку неалфавитноцифровых символов по их кодам,
            // чтобы не связываться с интерпретируемыми символами
            var nonAlphanumericCharactersString = 
                String.fromCharCode(33,34,35,36,37,38,39,40,41,42,43,44,45,46,
                                    58,59,60,61,62,63,64,91,92,93,94,95,123,124,125,126);             
            for(var ch in data)
            {
                if(nonAlphanumericCharactersString.indexOf(data[ch]) != -1)
                {
                    // Пароль допустим 
                    args.IsValid = true;           
                    return;
                }
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Добавление пользователей в БД</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h2 id="H2_1" runat="server">
                    Введите учетную запись для добавления пользователя</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="364px">
                    <table cellpadding="5" style="width: 137%">
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                Имяnbsp;пользователя:
                            </td>
                            <td>
                                nbsp;<asp:TextBox ID="username" runat="server" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="usernameRequiredValidator" 
                                    runat="server" ControlToValidate="username" 
                                    ErrorMessage='Не заполнено поле "Имя пользователя"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                nbsp;Пароль:
                            </td>
                            <td>
                                nbsp;<asp:TextBox ID="password" runat="server" />
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="passwordRequiredValidator" 
                                    runat="server" ControlToValidate="password" 
                                    ErrorMessage='Не заполнено поле "Пароль"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                                <asp:CustomValidator ID="passwordValidator" runat="server" 
                                    ControlToValidate="password"
                                    Display="Dynamic" 
                                    ClientValidationFunction="ValidatePassword" 
                                    OnServerValidate="passwordValidator_ServerValidate">*</asp:CustomValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                E-mail:</td>
                            <td>
                                nbsp;<asp:TextBox ID="email" runat="server"></asp:TextBox>
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="emailRequiredValidator" 
                                    runat="server" ControlToValidate="email" 
                                    ErrorMessage='Не заполнено поле "E-mail"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                                <asp:RegularExpressionValidator ID="emailValidator" runat="server" 
                                    ControlToValidate="email" ErrorMessage="Неверный E-mail" 
                                    ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" 
                                    Display="Dynamic">*</asp:RegularExpressionValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                Контрольный вопрос:</td>
                            <td>
                                nbsp;<asp:TextBox ID="question" runat="server"></asp:TextBox>
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="questionRequiredValidator" 
                                    runat="server" ControlToValidate="question" 
                                    ErrorMessage='Не заполнено поле "Контрольный вопрос"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                nbsp;nbsp;nbsp;
                            </td>
                            <td align="right" height="43" style="width: 224px">
                                Контрольный ответ:</td>
                            <td>
                                nbsp;<asp:TextBox ID="answer" runat="server"></asp:TextBox>
                            </td>
                            <td>
                                <asp:RequiredFieldValidator ID="answerRequiredValidator" 
                                    runat="server" ControlToValidate="answer" 
                                    ErrorMessage='Не заполнено поле "Контрольный ответ"' 
                                    Display="Dynamic">*</asp:RequiredFieldValidator>
                            </td>
                        </tr>
                        <tr>
                            <td colspan="4" align="center">
                                <asp:Button ID="btnAddUser" runat="server" 
                                    Text="Добавить" OnClick="AddUserMembership_Click" />
                            </td>
                        </tr>
                    </table>
                </asp:Panel>
                <asp:Label ID="lblResult" runat="server" Font-Bold="True" 
                    Font-Size="Large" ForeColor="Blue" />
            </div>
            <asp:ValidationSummary ID="ValidationSummary1" runat="server" />
        </form>
    </body>
    </html>
  • Выполните страницу AddUserMembership.aspx и убедитесь в работоспособности рассматриваемого механизма добавления пользователей в БД SQL Server
  • Упражнение 6. Административная страница удаления пользователей из БД

    Теперь рассмотрим вопрос удаления пользователей. Он намного проще, чем создание. Нужно только применить метод System.Web.Security.Membership.DeleteUser(), который имеет две перегрузки:

  • public static bool DeleteUser( string username ) - удаляет по имени пользователя все связанные с ним данные из всех таблиц БД
  • public static bool DeleteUser( string username, bool deleteAllRelatedData ) - удаляет пользователя только из таблицы aspnet_Users, если флаг равен false. Иначе удаляет все связанные с пользователем данные при флаге равном true
  • Обе перегрузки возвращают true, если пользователь успешно удален, иначе false.

  • Разработайте из копии страницы MyLogin.aspx административную страницу удаления пользователей DeleteUserMembership.aspx, которая будет примерно такой
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        protected void DeleteUser_Click(object sender, EventArgs e)
        {
            if (Membership.DeleteUser(username.Text))
            {
                lblResult.Text="Пользователь " + username.Text 
                    + " успешно удален";
                username.Text = "";
            }
            else
            {
                lblResult.Text = "Пользователь не удален";
                lblResult.ForeColor = System.Drawing.Color.Red;
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Удаление пользователей из БД</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <h2>
                    Введите имя удаляемого пользователя</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table cellpadding="5" style="width: 100%">
                        <tr>
                            <td align="right" height="43" style="width: 167px">
                                Имяnbsp;пользователя:</td>
                            <td>
                                nbsp;<asp:TextBox ID="username" runat="server" />
                            </td>
                        </tr>
                    </table>
                    <asp:Button ID="Button1" runat="server" Text="Удалить" 
                        OnClick="DeleteUser_Click" /></asp:Panel>
                <asp:Label ID="lblResult" runat="server" Font-Bold="True" 
                    Font-Size="Large" ForeColor="Blue">
                </asp:Label>
            </div>
        </form>
    </body>
    </html>
  • Испытайте страницу удаления пользователей
  • Упражнение 7. Административная страница извлечения списков пользователей из БД

    Для получения списка пользователей применяют метод Membership.GetAllUsers(), а для извлечения конкретного пользователя - метод Membership.GetUser().

    Рассмотрим пример, в котором будем использовать элемент GridView для представления пользователей в виде таблицы.

  • Добавьте к проекту страницу GetUsersMembership.aspx без файла отделенного кода и назначьте ее стартовой
  • Заполните страницу следующим кодом
  • <%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        // Вынесли поле класса для видимости в обработчиках
        MembershipUserCollection myUsers = new MembershipUserCollection();
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Скрываем таблицу деталей
            tableDetails.Visible = false;
            lblUser.Visible = false;
        
            if (checkUser.Checked)
            {
                // Извлекаем одного пользователя
                if (txtUser.Text.Length == 0)
                {
                    lblUsersList.Text = "Введите имя пользователя";
                    return;
                }
        
                try // Попытка
                {
                    string user = Server.HtmlEncode(txtUser.Text);
                    myUsers = new MembershipUserCollection();
                    myUsers.Add(Membership.GetUser(user));
                }
                catch // Откат
                {
                    lblUsersList.Text = "Пользователь не найден";
                    return;
                }
            }
            else
            {
                // Извлекаем всех пользователей
                myUsers = Membership.GetAllUsers();
            }
        
            // Подключаем к сетке извлеченную информации и наполняем элемент
            UsersGridView.DataSource = myUsers;
            UsersGridView.DataBind();
        }
        
        // Обработчик щелчков на ссылки столбца отображения деталей
        protected void UsersGridView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (UsersGridView.SelectedIndex >= 0)
            {
                // Заполняем интерфейсные элементы отображаемой информацией,
                // адресуясь к конкретному пользователю по ключевому имени,
                // заданному параметром DataKeyNames="UserName" элемента GridView 
                MembershipUser currentUser = 
                    myUsers[(string)UsersGridView.SelectedValue];
                lblUserName.Text = currentUser.UserName;
                lblEmail.Text = currentUser.Email;
                lblPasswordQuestion.Text = currentUser.PasswordQuestion;
                lblLastLoginDate.Text = currentUser.LastLoginDate.ToLongDateString();
                txtComment.Text = currentUser.Comment;
                checkIsApproved.Checked = currentUser.IsApproved;
                checkIsLockedOut.Checked = currentUser.IsLockedOut;
                
                // Включаем видимость таблицы деталей
                tableDetails.Visible = true;
                lblUser.Visible = true;
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <h2>
                    <asp:Label ID="lblUsersList" runat="server" Font-Bold="true" 
                        Text="Список зарегистрированных пользователей"></asp:Label>
                </h2>
                <p>Конкретный пользователь: 
                    <asp:CheckBox ID="checkUser" runat="server" />
                    <asp:TextBox ID="txtUser" runat="server"></asp:TextBox>
                    <asp:Button ID="btnUser" runat="server" Text="Получить" /></p>
                <asp:GridView ID="UsersGridView" runat="server" 
                    DataKeyNames="UserName" 
                    AutoGenerateColumns="False" 
                    OnSelectedIndexChanged="UsersGridView_SelectedIndexChanged">
                    <Columns>
                        <asp:BoundField DataField="UserName" HeaderText="Имя" />
                        <asp:BoundField DataField="Email" HeaderText="email" />
                        <asp:BoundField DataField="CreationDate" HeaderText="Создан" />
                        <asp:CommandField ShowSelectButton="True" HeaderText="Детали" 
                            SelectText="Показать" />
                    </Columns>
                </asp:GridView>
                <h2>
                    <asp:Label ID="lblUser" runat="server" 
                        Text="Подробности для выделенного пользователя"
                        Visible="False" Font-Bold="true"></asp:Label>
                </h2>            
                <table id="tableDetails" bordercolor="#0000FF" 
                    runat="server">
                    <tr>
                        <td width="50%">
                            Пользователь:</td>
                        <td>
                            <asp:Label ID="lblUserName" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            email:</td>
                        <td>
                            <asp:Label ID="lblEmail" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Контрольный вопрос:</td>
                        <td>
                            <asp:Label ID="lblPasswordQuestion" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Дата последней регистрации:</td>
                        <td>
                            <asp:Label ID="lblLastLoginDate" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Комментарии:</td>
                        <td>
                            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" 
                               ></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Проверенный:</td>
                        <td>
                            <asp:CheckBox ID="checkIsApproved" runat="server" /></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Заблокирован:</td>
                        <td>
                            <asp:CheckBox ID="checkIsLockedOut" runat="server" /></td>
                    </tr>
                </table>
            </div>
        </form>
    </body>
    </html>

    Интерфейсная часть страницы представлена элементом GridView и текстовыми метками, спозиционированными в серверной таблице. Значения параметров DataField элемента GridView представляют собой имена полей таблиц aspnet_Users и aspnet_Membership базы данных

    Установка в элементе управления GridView свойства DataKeyNames="UserName" позволяет извлекать значения поля UserName через GridView.SelectedValue, что очень удобно при получении подробностей о конкретном пользователе из коллекции пользователей.

    Метод Membership. GetUser() возвращает объект запрошенного пользователя, свойства которого мы отображаем в текстовых элементах, скомпонованных в таблице. Привязка к свойствам объекта пользователя выполняется в обработчике события SelectedIndexChanged элемента управления GridView.

    Результат работы страницы GetUsersMembership.aspx будет примерно таким

    Упражнение 8. Административная страница обновления пользователей в БД

    Если у нас есть экземпляр класса MembershipUser, то мы можем обновить его свойства, имеющие аксессор set, а именно:

  • Comment
  • Email
  • IsApproved
  • LastActivityDate
  • LastLoginDate
  • Для этого используется статический метод Membership. UpdateUser().

  • Получите из страницы GetUsersMembership.aspx копию с именем UpdateUserMembership.aspx
  • Скорректируйте страницу UpdateUserMembership.aspx, чтобы обновляемые параметры таблицы деталей размещались в текстовых полях
  • Закрепите за полем txtEmail элементы проверки достоверности RequiredFieldValidator и RegularExpressionValidator
  • Окончательный код страницы UpdateUserMembership.aspx будет таким

    <%@ Page Language="C#" EnableViewState="false" %>
    <%@ Import Namespace="System.Web.Security" %>
        
    <script runat="server">
        
        // Вынесли поле для видимости в обработчиках
        MembershipUserCollection myUsers = new MembershipUserCollection();
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Скрываем таблицу деталей
            tableDetails.Visible = false;
            lblUser.Visible = false;
        
            // Извлекаем всех пользователей
            myUsers = Membership.GetAllUsers();
        
            // Подключаем к сетке извлеченную информации и наполняем элемент
            UsersGridView.DataSource = myUsers;
            UsersGridView.DataBind();
        }
        
        // Обработчик щелчков на ссылки столбца отображения деталей
        protected void UsersGridView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (UsersGridView.SelectedIndex >= 0)
            {
                // Заполняем интерфейсные элементы отображаемой информацией,
                // адресуясь к конкретному пользователю по ключевому имени,
                // заданному параметром DataKeyNames="UserName" элемента GridView 
                MembershipUser currentUser = 
                    myUsers[(string)UsersGridView.SelectedValue];
                lblUserName.Text = currentUser.UserName;
                txtEmail.Text = currentUser.Email;
                lblPasswordQuestion.Text = currentUser.PasswordQuestion;
                lblLastLoginDate.Text = currentUser.LastLoginDate.ToLongDateString();
                txtComment.Text = currentUser.Comment;
                checkIsApproved.Checked = currentUser.IsApproved;
                checkIsLockedOut.Checked = currentUser.IsLockedOut;
                
                // Включаем видимость таблицы деталей
                tableDetails.Visible = true;
                lblUser.Visible = true;
            }
        }
        
        protected void btnUpdate_Click(object sender, EventArgs e)
        {
            if (UsersGridView.SelectedIndex >= 0)
            {
                // Адресуемся к выделенному пользователю
                MembershipUser currentUser =
                    myUsers[(string)UsersGridView.SelectedValue];
                    
                // Избавляемся от возможных управляющих символов
                // в пользовательском вводе 
                currentUser.Email = Server.HtmlEncode(txtEmail.Text);
                currentUser.Comment = Server.HtmlEncode(txtComment.Text);
                    
                currentUser.IsApproved = checkIsApproved.Checked;
                
                // Сбрасываем в базу данных 
                Membership.UpdateUser(currentUser);
                
                // Обновить содержимое GridView из БД, поскольку
                // этот обработчик выполнится после Page_Load
                myUsers = Membership.GetAllUsers();// Извлекаем пользователей
                UsersGridView.DataBind();
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <h2>
                    <asp:Label ID="lblUsersList" runat="server" Font-Bold="true" 
                        Text="Список зарегистрированных пользователей"></asp:Label>
                </h2>
                <asp:GridView ID="UsersGridView" runat="server" 
                    DataKeyNames="UserName" 
                    AutoGenerateColumns="False" 
                    OnSelectedIndexChanged="UsersGridView_SelectedIndexChanged">
                    <Columns>
                        <asp:BoundField DataField="UserName" HeaderText="Имя" />
                        <asp:BoundField DataField="Email" HeaderText="email" />
                        <asp:BoundField DataField="CreationDate" HeaderText="Создан" />
                        <asp:CommandField ShowSelectButton="True" HeaderText="Детали" 
                            SelectText="Показать" />
                    </Columns>
                </asp:GridView>
                <h2>
                    <asp:Label ID="lblUser" runat="server" 
                        Text="Подробности для выделенного пользователя"
                        Visible="False" Font-Bold="true"></asp:Label>
                </h2>            
                <table id="tableDetails" bordercolor="#0000FF" 
                    runat="server">
                    <tr>
                        <td width="50%">
                            Пользователь:</td>
                        <td>
                            <asp:Label ID="lblUserName" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font>email:</font></td>
                        <td>
                            <asp:TextBox ID="txtEmail" runat="server">
                            </asp:TextBox>
                            <asp:RequiredFieldValidator
                                ID="RequiredFieldValidator1" runat="server" 
                                ControlToValidate="txtEmail" Display="Dynamic">*
                            </asp:RequiredFieldValidator>
                            <asp:RegularExpressionValidator ID="RegularExpressionValidator1" 
                                runat="server" ControlToValidate="txtEmail"
                                Display="Dynamic" 
                                ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">*
                            </asp:RegularExpressionValidator></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Контрольный вопрос:</td>
                        <td>
                            <asp:Label ID="lblPasswordQuestion" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Дата последней регистрации:</td>
                        <td>
                            <asp:Label ID="lblLastLoginDate" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font>Комментарии:</font></td>
                        <td>
                            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" 
                               ></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font>Проверенный:</font></td>
                        <td>
                            <asp:CheckBox ID="checkIsApproved" runat="server" /></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            Заблокирован:</td>
                        <td>
                            <asp:CheckBox ID="checkIsLockedOut" runat="server" /></td>
                    </tr>
                    <tr>
                        <td colspan="2" align="center">
                            <asp:Button ID="btnUpdate" runat="server" Text="Обновить" 
                                OnClick="btnUpdate_Click" /></td>
                    </tr>
                </table>
            </div>
        </form>
    </body>
    </html>

    Элементы управления вкладки Login

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

    Это следующие компоненты:

  • Login - предоставляет два поля для ввода имени пользователя и пароля, а также кнопку для отправки на проверку удостоверения пользователя. По умолчанию проверка выполняется через API Membership
  • LoginView - позволяет отображать разные наборы элементов управления для разных категорий пользователей
  • PasswordRecovery - предоставляет необходимый интерфейс для запроса контрольного ответа и обеспечивает повторную отправку пользователю утраченного пароля через электронную почту
  • LoginStatus - проверяет состояние аутентификации текущего сеанса и предоставляет страницу регистрации для неаутентифицированных пользователей
  • LoginName - элемент управления, который в заданном формате отображает имя только того пользователя, который успешно прошел регистрацию
  • CreateUserWizard - предоставляет многошаговый пользовательский интерфейс для ввода имени, пароля, контрольных вопроса и ответа, адреса элекстронной почты, необходимых при регистрации на сайте
  • ChangePassword - предоставляет пользовательский интерфейс для смены пароля
  • Все эти элементы управления по умолчанию настроены на работу с API Membership.

    Упражнение 9. Административная страница регистрации на базе элемента управления Login

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

    Пока не закончена...
    Вернуться к учебному плану