Программирование на ASP.NET

Безопасность

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

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

Файлы к лекции Вы можете скачать здесь.

Безопасность включает в себя ряд мероприятий, необходимых для предотвращения несанкционированного доступа к сайту и защиты информации. Сюда входят аутентификация (распознавание) и авторизация (управление правами доступа к важным ресурсам) пользователя, шифрование, хэширование и цифровые подписи. Для всего этого 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-приложения командой
  • Добавьте к приложению конфигурационный файл
  • Заполните конфигурационный файл следующим кодом
    (рис ) Код конфигурационного файла<?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>
  • Добавьте к проекту страницу без отделенного кода с именем 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 
            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>
    Обратите внимание. Чтобы не использовать длинные имена классов в страницах с совмещенным кодом, можно подключить вначале страницы директиву
    <%@ Import Namespace="System.Web.Configuration" %>
  • В режиме редактирования страницы LoginAddUsers.aspx выполните команду File/View in Browser
  • Эта страница программно добавит новых пользователей в файл Web.config. Также будут скорректированы имена старых пользователей путем приведения символов к нижнему регистру. Но это не существенно, поскольку 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 со следующим кодом
    (рис ) Страница Default.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>
  • Создание страницы регистрации

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

    Страница регистрации должна включать в себя поля ввода для получения от пользователя данных удостоверения. Для проверки достоверности пользовательских данных необходимо использовать валидаторы, генерирующие нужный 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">
                <h2>
                    Введите свои имя и пароль</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table border="0" cellpadding="5" cellspacing="0" 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>

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

    Хеширование паролей в Web.config

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

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

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

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

    string hashedPwd = FormsAuthentication.HashPasswordForStoringInConfigFile(clearTextPassword, "MD5");
  • Добавьте к проекту страницу HashPasswordPage.aspx и наполните ее следующим кодом
    (рис ) Код страницы HashPasswordPage.aspx хеширования паролей в Web.config <%@ Page Language="C#" %>
        
    <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 
            System.Web.Configuration.AuthenticationSection authSec =
                (System.Web.Configuration.AuthenticationSection)
                systemWeb.Sections["authentication"];
            
            // Установить параметр passwordFormat, сообщающий, что было хеширование по MD5
            authSec.Forms.Credentials.PasswordFormat = System.Web.Configuration.FormsAuthPasswordFormat.MD5;
            
            // Извлекаем из коллекции Users старые имена и пароли 
            int count = authSec.Forms.Credentials.Users.Count;
            string[] name = new string[count];
            string[] clearTextPwd = new string[count];
            int i = 0;
            foreach (System.Web.Configuration.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 System.Web.Configuration.FormsAuthenticationUser(name[i], hashedPwd));
            }
            
            // Обновляем файл Web.config 
            myConfig.Save();
            
            // Отсылаем сообщение 
            lblHashResult.Text = "Хеширование завершено.<br />"
                + "Повторно не выполнять - будет хеш на хеш!!!";
        }
    </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="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>

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

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

    До сих пор мы использовали временные 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 без файла отделенного кода и заполните ее так
    (рис ) Страница создания на браузере постоянного cookie-набора аутентификации<%@ 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

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

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

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

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

    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, если в конфигурационном файле прописана секция аутентификации форм.

  • Создайте новый пустой проект командой
  • Откройте окно копирования проектов командой
  • Тестовые страницы 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>

    Код страницы регистрации MyLogin.aspx мы рассмотрим чуть позже, вместе с введенными изменениями.

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

    <?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

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

    Добавление в хранилище БД новых пользователей через утилиту 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
  • Страница регистрации

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

  • Модифицируйте страницу регистрации 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),
                // записать в него  метку аутентификации и перенаправить
                // на исходную запрошенную страницу или 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">
                <h2>
                    Введите свои имя и пароль</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table border="0" cellpadding="5" cellspacing="0" 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, можно программно решать, например, следующие задачи:

  • Создание, редактирование и удаление пользователей
  • Извлечение списков пользователей со всей подробной информацией, занесенной при создании
  • Создание и удаление пользователей

    Для создания и удаления пользователей следует применить перегруженные статические методы 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);
  • Переделайте интерфейсную и кодовую части страницы, чтобы она стала такой
    (рис ) Административная страница AddUserMembership.aspx добавления пользователей в БД<%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        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 border="0" cellpadding="5" cellspacing="0" 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
  • Теперь рассмотрим вопрос удаления пользователей. Он намного проще, чем создание. Нужно только применить метод 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, которая будет примерно такой
    (рис ) Административная страница 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 border="0" cellpadding="5" cellspacing="0" 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>
  • Испытайте страницу удаления пользователей
  • Извлечение из хранилища списков пользователей

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

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

  • Добавьте к проекту страницу GetUsersMembership.aspx и назначьте ее стартовой
  • Заполните страницу следующим кодом
    (рис ) Код страницы 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" border="1" bordercolor="#0000FF" 
                    width="100%" 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 valign="top" width="50%">
                            Комментарии:</td>
                        <td>
                            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" 
                                Width="100%"></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 будет примерно таким

    Конфигурирование поставщика SqlMembershipProvider

    Извлечение пароля напрямую через свойство не предусмотрено в классе MembershipUser. Для этого существует метод MembershipUser.GetPassword(), который работает только тогда, когда поставщик SqlMembershipProvider сконфигурирован с параметром enablePasswordRetrieval="true" в конфигурационном файле Web.config.

    Наша задача - перенастроить поставщик по умолчанию, создав для этого поставщик того же типа, но с другими настройками. Новый поставщик в той же базе данных будет иметь доступ только к своим данным. Включение параметра enablePasswordRetrieval="true" несовместимо с настройкой passwordFormat="Hashed", а должно иметь значения "Clear" или "Encrypted "

  • Измените конфигурационный файл, чтобы он принял следующий вид
    (рис ) Конфигурационный файл с новыми настройками поставщика SqlMembershipProvider<?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>
    		-->
    		<membership defaultProvider="MyMembershipProvider">
    			<providers>
    				<add name="MyMembershipProvider" connectionStringName="MyMembershipConnString"
    				 enablePasswordReset="true" requiresQuestionAndAnswer="true"
    				 requiresUniqueEmail="false" enablePasswordRetrieval="true" passwordFormat="Clear" 
    				 type="System.Web.Security.SqlMembershipProvider"
    				/>
    			</providers>
    		</membership>
    	</system.web>
    	<connectionStrings>
    		<add name="MyMembershipConnString"
    			connectionString="Data Source=localhost; Initial Catalog=ASPNETDB; user id=sa; password=;"
    		/>
    	</connectionStrings>
    </configuration>
  • Добавьте новых пользователей с помощью нашей административной страницы AddUserMembership.aspx
  • Получите из страницы GetUsersMembership.aspx копию с именем GetUsersMyMembershipProvider.aspx, назначьте ее стартовой и добавьте в интерфейсную часть текстовую метку lblPassword для отображения пароля
  • Добавьте в обработчик события SelectedIndexChanged строку заполнения метки
    lblPassword.Text = currentUser.GetPassword();
  • Обновление пользователей в хранилище

    Если у нас есть экземпляр класса 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" border="1" bordercolor="#0000FF" 
                    width="100%" runat="server">
                    <tr>
                        <td width="50%">
                            Пользователь:</td>
                        <td>
                            <asp:Label ID="lblUserName" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font color="#0000FF">email:</font></td>
                        <td>
                            <asp:TextBox ID="txtEmail" runat="server" Width="100%">
                            </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 valign="top" width="50%">
                            <font color="#0000FF">Комментарии:</font></td>
                        <td>
                            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" 
                                Width="100%"></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font color="#0000FF">Проверенный:</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>
    Страницы:

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

    Файлы к лекции Вы можете скачать здесь.

    Безопасность включает в себя ряд мероприятий, необходимых для предотвращения несанкционированного доступа к сайту и защиты информации. Сюда входят аутентификация (распознавание) и авторизация (управление правами доступа к важным ресурсам) пользователя, шифрование, хэширование и цифровые подписи. Для всего этого 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-приложения командой
  • Добавьте к приложению конфигурационный файл
  • Заполните конфигурационный файл следующим кодом
    (рис ) Код конфигурационного файла<?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>
  • Добавьте к проекту страницу без отделенного кода с именем 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 
            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>
    Обратите внимание. Чтобы не использовать длинные имена классов в страницах с совмещенным кодом, можно подключить вначале страницы директиву
    <%@ Import Namespace="System.Web.Configuration" %>
  • В режиме редактирования страницы LoginAddUsers.aspx выполните команду File/View in Browser
  • Эта страница программно добавит новых пользователей в файл Web.config. Также будут скорректированы имена старых пользователей путем приведения символов к нижнему регистру. Но это не существенно, поскольку 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 со следующим кодом
    (рис ) Страница Default.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>
  • Создание страницы регистрации

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

    Страница регистрации должна включать в себя поля ввода для получения от пользователя данных удостоверения. Для проверки достоверности пользовательских данных необходимо использовать валидаторы, генерирующие нужный 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">
                <h2>
                    Введите свои имя и пароль</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table border="0" cellpadding="5" cellspacing="0" 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>

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

    Хеширование паролей в Web.config

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

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

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

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

    string hashedPwd = FormsAuthentication.HashPasswordForStoringInConfigFile(clearTextPassword, "MD5");
  • Добавьте к проекту страницу HashPasswordPage.aspx и наполните ее следующим кодом
    (рис ) Код страницы HashPasswordPage.aspx хеширования паролей в Web.config <%@ Page Language="C#" %>
        
    <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 
            System.Web.Configuration.AuthenticationSection authSec =
                (System.Web.Configuration.AuthenticationSection)
                systemWeb.Sections["authentication"];
            
            // Установить параметр passwordFormat, сообщающий, что было хеширование по MD5
            authSec.Forms.Credentials.PasswordFormat = System.Web.Configuration.FormsAuthPasswordFormat.MD5;
            
            // Извлекаем из коллекции Users старые имена и пароли 
            int count = authSec.Forms.Credentials.Users.Count;
            string[] name = new string[count];
            string[] clearTextPwd = new string[count];
            int i = 0;
            foreach (System.Web.Configuration.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 System.Web.Configuration.FormsAuthenticationUser(name[i], hashedPwd));
            }
            
            // Обновляем файл Web.config 
            myConfig.Save();
            
            // Отсылаем сообщение 
            lblHashResult.Text = "Хеширование завершено.<br />"
                + "Повторно не выполнять - будет хеш на хеш!!!";
        }
    </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="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>

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

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

    До сих пор мы использовали временные 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 без файла отделенного кода и заполните ее так
    (рис ) Страница создания на браузере постоянного cookie-набора аутентификации<%@ 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

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

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

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

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

    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, если в конфигурационном файле прописана секция аутентификации форм.

  • Создайте новый пустой проект командой
  • Откройте окно копирования проектов командой
  • Тестовые страницы 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>

    Код страницы регистрации MyLogin.aspx мы рассмотрим чуть позже, вместе с введенными изменениями.

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

    <?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

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

    Добавление в хранилище БД новых пользователей через утилиту 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
  • Страница регистрации

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

  • Модифицируйте страницу регистрации 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),
                // записать в него  метку аутентификации и перенаправить
                // на исходную запрошенную страницу или 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">
                <h2>
                    Введите свои имя и пароль</h2>
                <asp:Panel ID="MainPanel" runat="server" BorderColor="Silver" 
                    BorderStyle="Ridge"
                    BorderWidth="2px" Height="90px" Width="412px">
                    <table border="0" cellpadding="5" cellspacing="0" 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, можно программно решать, например, следующие задачи:

  • Создание, редактирование и удаление пользователей
  • Извлечение списков пользователей со всей подробной информацией, занесенной при создании
  • Создание и удаление пользователей

    Для создания и удаления пользователей следует применить перегруженные статические методы 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);
  • Переделайте интерфейсную и кодовую части страницы, чтобы она стала такой
    (рис ) Административная страница AddUserMembership.aspx добавления пользователей в БД<%@ Page Language="C#" EnableViewState="false" %>
        
    <script runat="server">
        
        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 border="0" cellpadding="5" cellspacing="0" 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
  • Теперь рассмотрим вопрос удаления пользователей. Он намного проще, чем создание. Нужно только применить метод 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, которая будет примерно такой
    (рис ) Административная страница 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 border="0" cellpadding="5" cellspacing="0" 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>
  • Испытайте страницу удаления пользователей
  • Извлечение из хранилища списков пользователей

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

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

  • Добавьте к проекту страницу GetUsersMembership.aspx и назначьте ее стартовой
  • Заполните страницу следующим кодом
    (рис ) Код страницы 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" border="1" bordercolor="#0000FF" 
                    width="100%" 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 valign="top" width="50%">
                            Комментарии:</td>
                        <td>
                            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" 
                                Width="100%"></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 будет примерно таким

    Конфигурирование поставщика SqlMembershipProvider

    Извлечение пароля напрямую через свойство не предусмотрено в классе MembershipUser. Для этого существует метод MembershipUser.GetPassword(), который работает только тогда, когда поставщик SqlMembershipProvider сконфигурирован с параметром enablePasswordRetrieval="true" в конфигурационном файле Web.config.

    Наша задача - перенастроить поставщик по умолчанию, создав для этого поставщик того же типа, но с другими настройками. Новый поставщик в той же базе данных будет иметь доступ только к своим данным. Включение параметра enablePasswordRetrieval="true" несовместимо с настройкой passwordFormat="Hashed", а должно иметь значения "Clear" или "Encrypted "

  • Измените конфигурационный файл, чтобы он принял следующий вид
    (рис ) Конфигурационный файл с новыми настройками поставщика SqlMembershipProvider<?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>
    		-->
    		<membership defaultProvider="MyMembershipProvider">
    			<providers>
    				<add name="MyMembershipProvider" connectionStringName="MyMembershipConnString"
    				 enablePasswordReset="true" requiresQuestionAndAnswer="true"
    				 requiresUniqueEmail="false" enablePasswordRetrieval="true" passwordFormat="Clear" 
    				 type="System.Web.Security.SqlMembershipProvider"
    				/>
    			</providers>
    		</membership>
    	</system.web>
    	<connectionStrings>
    		<add name="MyMembershipConnString"
    			connectionString="Data Source=localhost; Initial Catalog=ASPNETDB; user id=sa; password=;"
    		/>
    	</connectionStrings>
    </configuration>
  • Добавьте новых пользователей с помощью нашей административной страницы AddUserMembership.aspx
  • Получите из страницы GetUsersMembership.aspx копию с именем GetUsersMyMembershipProvider.aspx, назначьте ее стартовой и добавьте в интерфейсную часть текстовую метку lblPassword для отображения пароля
  • Добавьте в обработчик события SelectedIndexChanged строку заполнения метки
    lblPassword.Text = currentUser.GetPassword();
  • Обновление пользователей в хранилище

    Если у нас есть экземпляр класса 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" border="1" bordercolor="#0000FF" 
                    width="100%" runat="server">
                    <tr>
                        <td width="50%">
                            Пользователь:</td>
                        <td>
                            <asp:Label ID="lblUserName" runat="server"></asp:Label></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font color="#0000FF">email:</font></td>
                        <td>
                            <asp:TextBox ID="txtEmail" runat="server" Width="100%">
                            </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 valign="top" width="50%">
                            <font color="#0000FF">Комментарии:</font></td>
                        <td>
                            <asp:TextBox ID="txtComment" runat="server" TextMode="MultiLine" 
                                Width="100%"></asp:TextBox></td>
                    </tr>
                    <tr>
                        <td width="50%">
                            <font color="#0000FF">Проверенный:</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>
    Вернуться к учебному плану