Защитите свои веб приложения
По материалам статьи Timothy M. Chester: Secure
Your Web Apps
Перевод Максима Зубова
1. Повысьте
гибкость и безопасность своих веб приложений используя
FormsAuthenticationModule
2. Защитите
свое приложение
3. Определяем
режим аутентификации на основе форм
4. Кодирование
программной логики
5. C#
- Прием и проверка введенных пользователем учетных
данных
Повысьте гибкость и
безопасность своих веб приложений используя
FormsAuthenticationModule
Аутентификация, основанная на использовании cookie
популярна среди разработчиков, использующих Active Server
Pages (ASP) потому, что это довольно простой способ,
обладающий к тому же достаточной гибкостью. Однако такой
способ аутентификации имеет существенный недостаток, cookies
довольно просто обмануть и поэтому им не стоит особенно
доверять. Любое веб приложение, безопасность которого основана
на применении этого способа аутентификации подвержено риску.
Корпорация Microsoft отреагировала на эту проблему внедрением
более надежного и сложного механизма аутентификации на основе
форм в ASP.NET (ASPX).
В это статье я покажу, как
использовать класс System.Web.Security.FormsAuthentication из
.NET Framework для реализации системы аутентификации на основе
XML. Вы увидите, как, используя XML, надежно аутентифицировать
клиента, предполагая, что веб приложение само по себе защищено
от неавторизованных пользователей. Пример веб приложения,
которое мы создадим, будет написан на C#, но вы можете
применять эти принципы аутентификации, используя любой другой
язык .NET.
.NET Framework и Visual Studio .NET предлагают
вам всю необходимую инфраструктуру, которая может
потребоваться для решения проблем безопасности, потоков,
управления памятью. Эти инструменты, вместе с аутентификацией
на основе форм в ASP.NET, освобождают вас от программирования
низкоуровневых алгоритмов, позволяя сосредоточиться на
написании кода, описывающего бизнес функции.
Традиционный
ASP требовал от вас написание кода в пять шагов, тогда как в
ASPX (см. рис. 1) вам потребуется описать только два шага.
ASP.NET реализует большинство действий автоматически, включая
инициализацию и определение cookies, что обеспечивает
значительное увеличение производительности труда
разработчиков.
Рис. 1. Сравнение ASP и ASP.NET
аутентификации.
Диаграмма показывает сравнение способов аутентификации на
основе форм в традиционном ASP и ASP.NET (ASPX). Шаги, которые
приходится программировать вручную, показаны более темными
фигурами, в то время как шаги, которые ASP или ASPX выполняет
автоматически - более светлыми.
Пространство имен
System.Web.Security в .NET Framework предлагает несколько
классов, которые используются при решении вопросов
безопасности. Класс FormsAuthenticationModule() предоставляет
функциональность, необходимую при разработке аутентификации на
основе форм. Когда конечный пользователь запрашивает ASPX
страницу, его контекст безопасности по умолчанию - анонимный
пользователь (anonymous user). Если запрашиваемый ресурс
защищен, то есть требует аутентификации, ASP.NET генерирует
стандартную ошибку "HTTP 401 - Unauthorized". Упомянутый здесь
класс FormsAuthenticationModule(), перехватывает эту ошибку,
затем конвертирует ее на лету в "HTTP 302 - Found", который в
свою очередь переадресует пользователя на страницу прохождения
процедуры аутентификации (например, на страницу ввода имени
пользователя и пароля). Все это происходит автоматически, без
необходимости написания какого либо кода разработчиком.
Вы
должны написать код, который проверяет достоверность учетной
записи пользователя, сформированной после его успешной
аутентификации. Затем вы вызываете метод
FormsAuthentication.RedirectFromLoginPage() если переданные
пользователем параметры достоверны. Этот метод направляет
пользователя назад к той странице, которую он изначально
запрашивал, отправляя cookie вместе с ответом об успешном
входе (successful logon). Cookie содержит информацию о версии,
имени пользователя, информацию о времени и другие
необязательные параметры. Cookie зашифрован и защищен при
помощи кода подлинности сообщения MAC (Message Authentication
Code). MAC работает в качестве зашифрованной контрольной
суммы. Содержимое cookie и MAC вычисляются при каждом
последующем запросе страниц. Любое расхождение между новым MAC
кодом и оригинальным, говорит о том, что cookie искажен и не
может более использоваться. Это очень важный момент, потому
что cookie хранит аутентифицированное имя пользователя. Если
cookie был изменен, то конечный пользователь может выдать себя
за любого другого пользователя.
Теперь используем на
практике полученные только что теоретические знания об
аутентификации на основе форм в ASP.NET. Я покажу, как
активировать аутентификацию на основе форм на стороне сервера.
Вы можете использовать эту информацию для создания XML базы
данных своих пользователей.
[В
начало]
Защитите свое
приложение
Включить аутентификацию на основе форм очень легко. Начните
с создания нового виртуального каталога на вашем веб сайте по
умолчанию (если вы не знаете, как это сделать, просмотрите
документацию к IIS). В примере
приложения эта папка называется "code". Конфигурируйте
свои ASP.NET приложения используя файлы XML формата. Файл,
который вам потребуется, называется Web.config, и он должен
находиться в корне вашего виртуального каталога. Можно также
создать файл для нескольких конфигураций. Каждый файл
относится только к своей папке и вложенным в нее папкам. Вы
можете вносить изменения в файл Web.config без перезагрузки
Internet Information Server (IIS) - ASP.NET сам определяет
изменения и начинает использовать их автоматически. ASP.NET
защищает этот файл от просмотра и автоматически генерирует
ошибку "HTTP 403 - Forbidden", если пользователь пытается
просмотреть его содержимое при помощи своего браузера.
Файл
Web.config для нашего демонстрационного приложения содержит
следующий код:
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
<authentication mode="Forms">
<forms name="CODE"
loginUrl="CreateAccount.aspx"
protection="All"
timeout="1"
path="/code"/>
</authentication>
</system.web>
</configuration> |
Сохраните этот файл в ту виртуальную папку, которую вы
только что создали. Элемент <configuration> служит
корневым элементом в каждом конфигурационном файле,
используемом приложениями .NET Framework. Элемент
<system.web> определяет информацию, которая относится к
приложениям ASP.NET. Приложения ASP.NET имеют очень большое
количество опций конфигурации, поэтому вы, возможно, потратите
не мало времени на изучение документации по .NET Framework для
понимания всех возможных опций элемента
<system.web>.
Внутри <system.web>, используйте
элемент <authorization> для указания того, какой способ
авторизации будет использоваться для доступа к содержимому
виртуальной папки "code". Схема для этого элемента выглядит
так:
<authorization>
<allow users=
"список пользователей через запятую"
roles=
"список ролей через запятую"
verbs="список команд через запятую" />
<deny users=
"список пользователей через запятую"
roles=
"список ролей через запятую "
verbs="список команд через запятую" />
</authorization> |
Вы можете сконфигурировать приложение, чтобы давать или не
давать пользователям доступ на основе их доменного имени, роли
или метода доступа (используя команды GET, HEAD, POST или
DEBUG). Вы можете использовать заменяющие символы (маску)
когда описываете любое значение элемента
<authorization>. Используйте следующий код, чтобы
запретить анонимный доступ к виртуальной папке:
<authorization>
<deny users="?" />
</authorization> |
Элемент <deny> сообщает ASP.NET чтобы доступ всем
анонимным пользователям был запрещен, что также относится к
пользователям, которые не прошли процедуру аутентификации.
Элемент <authorization> должен существовать и быть
вложенным в элемент <system.web>. Как только вы
сохраните сформированный таким образом файл Web.config, ваше
приложение будет защищено от доступа анонимных
пользователей.
Далее, вы должны определить методы, с
помощью которых будете аутентифицировать пользователей своего
приложения. Это делается внутри элемента
<authentication>, который также существует внутри
элемента <system.web>. Вот схема для этого элемента:
<authentication mode=
"Windows|Forms|Passport|None">
<forms name="имя"
loginUrl="url"
protection=
"All|None|Encryption|Validation"
timeout="30" path="/" >
<credentials
passwordFormat=
"Clear|SHA1|MD5">
<user name="имя пользователя"
password="пароль" />
</credentials>
</forms>
<passport redirectUrl=
"внутренний"/>
</authentication> |
Аутентификация - это процесс проверки переданных конечным
пользователем данных и идентификация его учетной записи, она
может проходить несколькими способами. Атрибут mode элемента
<authentication> определяет метод аутентификации для
виртуальной папки. Если вы используете аутентификацию на
основе форм, то используйте режим "Forms". Однако, Вы можете
также использовать аутентификацию Windows (Basic, Digest,
NTLM, Kerberos, или сертификаты), Microsoft Passport или даже
свой собственный пользовательский метод аутентификации внутри
своего приложения.
[В
начало]
Определяем режим
аутентификации на основе форм
Используйте этот пример XML кода для своего тестового
приложения. Этот код указывает, что вы собираетесь
использовать режим аутентификации на основе форм:
<authentication mode="Forms">
<forms name="CODE"
loginUrl="CreateAccount.aspx"
protection="All"
timeout="1"
path="/code"/>
</authentication> |
Атрибут name элемента <forms> определяет имя cookie,
который будет установлен в случае успешной аутентификации.
Значение по умолчанию - .ASPXAUTH. Если у вас запускается
несколько приложений на одном сервере, то вы должны давать
каждому приложению или виртуальной папке уникальное имя
cookie, используя этот атрибут. Атрибут loginUrl определяет
URL на который будет переадресован пользователь если не будет
найден корректный cookie. Атрибут protection определяет
уровень защиты (шифрование, основанная на MAC коде проверка)
для cookie, используемого при аутентификации. "All" - это
значение по умолчанию (рекомендуемое значение). Атрибут
timeout определяет количество минут, после истечения которых,
cookie считается устаревшим и более не может использоваться.
Вы можете использовать этот атрибут для требования повторной
регистрации после истечения заданного времени, в течение
которого пользователь не проявил активность. Срок годности
аутентификационного cookie не обновляется при каждом запросе
ASPX страниц, а обновляется при запросах страниц, которые
произошли по истечении половины срока годности cookie. Атрибут
path определяет путь, по которому в виртуальной папке хранится
файл cookie.
Сохраните предыдущий XML код в файл Web.config
в элементе <system.web>. Теперь пришло время создать
простое приложение для демонстрации функциональности, которую
мы только что рассмотрели.
Наше демонстрационное приложение
состоит из одной страницы (default.aspx) которая является
защищенной и ее просмотр возможен только после прохождения
процедуры аутентификации. Эта страница является страницей по
умолчанию вашей виртуальной папки. Дополнительный файл со
скриптом - CreateAccount.aspx - служит для ввода пользователем
своей учетной информации (см. Рис. 2).
Рис. 2. Вход пользователя или создание новой учетной
записи.
Страница CreateAccount.aspx представляет этот
пользовательский интерфейс нашего приложения. Пользователь
сможет использовать эту страницу для ввода своего имени и
пароля или для создания новой учетной записи.
Эта страница
содержит две секции: секцию для ввода имени пользователя и
пароля, для зарегистрированных пользователей и "create
account" для создания учетной записи нового пользователя. Наше
приложения также включает в себя XML файл users.xml, который
будет использоваться как база данных зарегистрированных
пользователей:
<users>
<user>
<name>tcheste</name>
<password>30000248979A05B9A58F9CCFFD
A3C5F8294E71BD</password>
<lastName>Chester</lastName>
<firstName>Tim</firstName>
<email>tim-chester@tamu.edu</email>
</user>
</users> |
Файл хранится в c:\users\users.xml, но вы можете
переместить его в любое место на жестком диске вашего
компьютера.
Когда анонимный пользователь запрашивает доступ
в вашу виртуальную папку (расположенную в
http://localhost/code), он автоматически будет переадресован
на CreateAccount.aspx. И адрес в строке браузера будет
выглядеть примерно так:
http://localhost/code/CreateAccount.aspx
?ReturnUrl=%2fcode%2fDefault.aspx |
ASP.NET, в нашем случае, автоматически добавил к
оригинальному адресу запрашиваемой страницы, адрес страницы
для регистрации - CreateAccount.aspx. Как только пользователь
успешно пройдет аутентификацию и метод
FormsAuthentication.RedirectFromLoginPage() выполнится внутри
вашей ASPX страницы, пользователь будет автоматически
перенаправлен к той странице, которую изначально запрашивал.
ASP.NET запоминает какую страницу изначально запросил
пользователь и после успешной аутентификации возвращает
ее.
Хеш код паролей создается по алгоритму SHA1 и хранится
в файле users.xml. Эта защита предохраняет пароли от просмотра
в файле users.xml. Класс FormsAuthentication содержит метод
HashPasswordForStoringInConfigFile(), который вы можете
использовать для хеширования паролей по алгоритму SHA1 или
MD5.
Когда пользователь вводит свое имя и пароль, страница
CreateAccount.aspx проверяет эти данные путем сравнения их с
информацией о существующих пользователях, содержащейся в XML
файле. Код загружает users.xml в объект XmlDocument, и
выполняет XPATH запрос к объектной модели документов (Document
Object Model - DOM), ища соответствие имени пользователя и
пароля. Если код CreateAccount.aspx находит совпадение,
выполняется метод
FormsAuthentication.RedirectFromLoginPage().ASPX устанавливает
cookie для аутентификации автоматически, и автоматически
перенаправляет пользователя на адрес ресурса, который он
изначально запросил.
CreateAccount.aspx также содержит
форму, которую можно использовать для создания новой учетной
записи. После того, как все необходимые поля заполнены, логика
внутри ASPX скрипта этой страницы определяет, существует ли
уже такой пользователь в users.xml. Если да, то запрашивается
новое имя пользователя. Если нет, то новый элемент
<user> добавляется в файл users.xml. Затем вызывается
FormsAuthentication.RedirectFromLoginPage(). И снова, ASPX
автоматически устанавливает соответствующий cookie и
перенаправляет пользователя к изначально запрошенному ресурсу
(см. Рис. 3).
Рис. 3. Это отображает страница default.aspx.
Эта страница отображается только при успешном прохождении
аутентификации.
[В
начало]
Кодирование программной
логики
Теперь взгляните на полный исходный текст
CreateAccount.aspx (см. Листинг 1). Вначале взгляните на HTML
часть этого скрипта. Обе секции, и "logon" и "create new"
находятся в одной ASPX форме. Внутри формы, теги
<asp:TextBox> представляют каждый текстовый элемент (имя
пользователя, пароль, имя и т.д.). ASP.NET выполняет код этих
элементов управления на стороне сервера и они, к тому же,
обладают гораздо большей гибкостью, чем соответствующие HTML
элементы управления. Заметьте, что CreateAccount.aspx также
использует <asp:RequiredFieldValidator> для проверки
введенных пользователем значений. Эти теги можно использовать
для множества других видов проверки правильности введенных
данных, например для сравнения введенных данных с
шаблонами.
[В
начало]
C# - Прием и проверка
введенных пользователем учетных данных
Листинг 1. Страница CreateAccount.aspx, код которой
представлен ниже, принимает введенную пользователем информацию
и сравнивает ее с XML файлом. Используйте класс
FormsAuthentication() для сообщения ASP.NET о том, что
состоялась успешная аутентификация, и пользователь может
продолжать работу с приложением.
<%@ Page language="c#" trace="true"%>
<%@ import namespace='System.Web.Security'%>
<%@ import namespace='System.Xml'%>
<html>
<head>
<title>Forms Authentication Sample</title>
</head>
<body>
<h1>Please enter your username and
password</h1>
<p><asp:Label id="Msg1" runat="server"
ForeColor="red"/></p>
<form runat='server'>
<table>
<tr>
<td>Your Username:</td>
<td><asp:TextBox TextMode=
"SingleLine"
id="username" runat="server"
Width="150"/></td>
</tr>
<tr>
<td>Your Password:</td>
<td><asp:TextBox
TextMode="Password"
id="password" runat="server"
Width="150"/></td>
</tr>
</table>
<p><asp:Button Text="Begin Session"
runat="server"
OnClick="AuthenticateUser"
CausesValidation="false"/></p>
<h1>Or, create your own account here!</h1>
<p><asp:Label id="Msg2" runat="server"
ForeColor="red"/></p>
<table>
<tr>
<td>Your First Name:</td>
<td>
<asp:TextBox TextMode=
"SingleLine"
id="realFirstName"
Width="150" runat="server"/>
<asp:RequiredFieldValidator
id="Required1"
ControlToValidate=
"realFirstName"
Text="Required Field!"
runat="server"/>
</td>
</tr>
<tr>
<td>Your Last Name:</td>
<td>
<asp:TextBox TextMode=
"SingleLine"
id="realLastName"
Width="150" runat="server"/>
<asp:RequiredFieldValidator
id="Required2"
ControlToValidate=
"realLastName"
Text="Required Field!"
runat="server"/>
</td>
</tr>
<tr>
<td>Your Email Address:</td>
<td>
<asp:TextBox TextMode=
"SingleLine" id="realEmail"
Width="150" runat="server"/>
<asp:RequiredFieldValidator
id="Required3"
ControlToValidate=
"realEmail"
Text="Required Field!"
runat="server"/>
</td>
</tr>
<tr>
<td>Enter a Username:</td>
<td>
<asp:TextBox TextMode=
"SingleLine"
id="realUserName"
Width="150" runat="server"/>
<asp:RequiredFieldValidator
id="Required4"
ControlToValidate=
"realUserName"
Text="Required Field!"
runat="server"/>
</td>
</tr>
<tr>
<td>Enter a Password:</td>
<td>
<asp:TextBox TextMode=
"SingleLine"
id="realPassword"
Width="150" runat="server"/>
<asp:RequiredFieldValidator
id="Required5"
ControlToValidate=
"realPassword"
Text="Required Field!"
runat="server"/>
</td>
</tr>
</table>
<p><asp:Button Text="Create Account"
runat="server" OnClick=
"CreateAccount"/></p>
</form>
</body>
</html>
<script runat="server">
//subprocedure that runs whenever the page
//executes
void Page_Load()
{
//clear out all my labels so that old
//messages are cleared out
Msg1.Text = "";
Msg2.Text = "";
}
//method to create an account when the create
//account button is pressed
void CreateAccount(Object sender, EventArgs
eventArgs)
{
//first load your user's document
//into an xml document
XmlDocument x = new XmlDocument();
x.Load("c:\\users\\users.xml");
//make sure that the username doesn't exist
//previously.
XmlNode xy = x.SelectSingleNode
("/users/user[name='" +
realUserName.Text + "']");
if (xy == null)
{
//this means that we haven't gotten a
//match for the name so let's create a
//new user element and add it to the
//tree
XmlNode n = x.CreateElement("user");
XmlNode n1 = x.CreateElement("name");
n1.InnerText=realUserName.Text;
n.AppendChild(n1);
XmlNode n2 =
x.CreateElement("password");
n2.InnerText=FormsAuthentication.
HashPasswordForStoringInConfigFile
(realPassword.Text,
"sha1");
n.AppendChild(n2);
XmlNode n3 =
x.CreateElement("lastName");
n3.InnerText=realLastName.Text;
n.AppendChild(n3);
XmlNode n4 =
x.CreateElement("firstName");
n4.InnerText=realFirstName.Text;
n.AppendChild(n4);
XmlNode n5 = x.CreateElement("email");
n5.InnerText=realEmail.Text;
n.AppendChild(n5);
XmlNode branch =
x.SelectSingleNode("/users");
branch.AppendChild(n);
//save the new xml user data back
//to a file
x.Save("c:\\users\\users.xml");
Session["name"] = realUserName.Text;
//then, you can simply redirect them
//back to the page they started from
FormsAuthentication.RedirectFromLoginPage(
realUserName.Text, false);
}
else
{
//if we get here the username exists
//previously
Msg2.Text =
"This username already exists!";
}
}
//method which can be used to authenticate
//each user
void AuthenticateUser(Object sender, EventArgs
eventArgs)
{
//write some code here to//authenticate
//against your user.xml file located in
//c:\users
//first load the user information file into
//an xml document
XmlDocument x = new XmlDocument();
x.Load("c:\\users\\users.xml");
//see if you get a match for the username
//and the hash of the password
XmlNode xy = x.SelectSingleNode
("/users/user[name='" + username.Text +
"'][password='"
+ FormsAuthentication.
HashPasswordForStoringInConfigFile
(password.Text, "sha1")
+ "']");
if (xy != null)
{
Session["name"] = username.Text;
//we got a match for the username and
//password so let them into the system
FormsAuthentication.
RedirectFromLoginPage(username.Text,
false);
}
else
{
Msg1.Text="Error: Unknown
username/password combination";
}
}
</script> |
Этот скрипт также содержит C# код для проверки того, что
данный пользователь уже существует или для регистрации нового
пользователя. Когда пользователь нажимает кнопку Begin
Session, выполняется метод AuthenticateUser(). Этот код служит
для проверки того, что данный пользователь уже
зарегистрирован. Метод AuthenticateUser() вначале загружает
файл users.xml в объект XmlDocument для проверки учетной
записи:
XmlDocument x = new XmlDocument();
x.Load("c:\\users\\users.xml"); |
Как только файл users.xml загружен, создается новей объект
XmlNode, и используется XPATH выражение для поиска
пользователя по совпадению имени пользователя и пароля внутри
users.xml:
XmlNode xy = x.SelectSingleNode
("/users/user[name='"
+ username.Text + "'][password='"
+ FormsAuthentication.
HashPasswordForStoringInConfigFile
(password.Text, "sha1") + "']"); |
Хранящийся в users.xml пароль, зашифрован, поэтому метод
AuthenticationUser() хеширует введенный пользователем пароль,
используя метод HashPasswordForStoringInConfigFile() объекта
FormsAuthentication.
Пользователь считается успешно
прошедшим аутентификацию, если метод AuthenticateUser()
находит совпадение. Тогда, все что остается сделать, это дать
понять ASPX, что пользователь успешно прошел процедуру
аутентификации. Это делается при помощи следующего кода:
FormsAuthentication.
RedirectFromLoginPage
(username.Text, false); |
Метод RedirectFromLoginPage() принимает два входных
параметра. Первый параметр, это имя пользователя, которое
должно быть зашифровано в создаваемом cookie. Второй параметр
является логическим выражением, которое определяет, должен ли
созданный аутентификационный cookie присутствовать в
нескольких сессиях браузера. Установите этот параметр в false,
если хотите, чтобы пользователь каждый раз при новом
подключении к вашему приложению должен был вводить свои
учетные данные. Будьте осторожны с этим, потому, что если вы
установите этот параметр в true, аутентификационный cookie
никогда не будет считаться устаревшим.
Метод
CreateAccount() вызывается, когда пользователь пытается
создать новую учетную запись. Этот метод проверяет, был ли уже
такой пользователь зарегистрирован, и если нет, то добавляет
новую запись о новом пользователе в файл users.xml. Сначала
код загружает файл users.xml в объект XmlDocument() и создает
новый XmlNode() используя выражение XPATH, которое ищет
соответствие параметров нового пользователя:
XmlNode xy = x.SelectSingleNode
("/users/user[name='"
+ realUserName.Text + "']"); |
Если CreateAccount() не находит соответствия, то создается
новый объект XmlNode(), который содержит информацию, введенную
новым пользователем при регистрации своей учетной записи. Этот
объект включает в себя имя, фамилию, адрес электронной почты,
имя пользователя (логин) и пароль. Метод
HashPasswordForStoringInConfigFile() хеширует пароль, перед
тем как он будет записан в файл. И, наконец, пользователь
направляется к защищенной странице (default.aspx), используя
метод RedirectFromLoginPage().
Приложение, которое мы
только что разработали очень легко конфигурируемо. Вы можете
переработать этот код для создания своей собственной системы
аутентификации, или, например, для использования Lightweight
Directory Access Protocol (LDAP). Также Вы можете использовать
при этом информацию, хранящуюся на Microsoft SQL Server. Код,
рассмотренный в этой статье, предоставляет прекрасную
возможность изучить один из способов защиты ваших веб
приложений. Используйте аутентификацию на основе форм, для
увеличения гибкости и защищенности ваших приложений.
[В
начало]