Аутентификация при использовании WebRequest и WebResponse (исходники)

Dimon aka Manowar

Все наверное уже знают как получить содержимое URL с помошью методов GET или POST используя классы WebRequest/WebResponse. Но бывают моменты, когда этого мало, например, когда для доступа к так желаемым данным необходимо пройти предварительно аутентификацию.

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

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

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

  1. Пользователь вводит свои данные и отсылает форму на сервер.
  2. Сервер проверяет есть ли пользователь с такими данными в базе и если все ок - возвращает в ответе какие-то куки (неважно какие, важно, что по ним потом проверяется, аутентифицирован ли пользователь).
  3. При запросе защищенной страницы сервер проверяет наличие нужных кук и если все ок - возвращает страницу.

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

Для начала необходимо сходить на нужный сайт ручками и посмотреть в коде переменные, которые посылаются формой при логине. Допустим их 3 - login, pwd и action (всегда равный login). Теперь можно начинать программировать.

Вначале все выглядит как в известном примере получения данных по POST запросу - создается экземпляр класса WebRequest с нужным URL, устанавливаются свойства ContentType и Method, создается строка парамеров, которую необходимо будет отправить на данный URL и затем отсылается туда. Возвращаемый сервером результат принимается в экземпляр класса HttpWebResponse.

HttpWebResponse result = null;
HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create("(URL для авторизации)");
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";

byte[] SomeBytes = null;
string FormParams = "Login=(имя пользователя)&Password=(пароль)&action=login";
SomeBytes = Encoding.UTF8.GetBytes(FormParams);
req.ContentLength = SomeBytes.Length;
Stream newStream = req.GetRequestStream();
newStream.Write(SomeBytes, 0, SomeBytes.Length);
newStream.Close();
result = (HttpWebResponse) req.GetResponse();

Теперь начинается самое интересное. Возвращаемые сервером куки передаются в заголовке ответа сервера с именем Set-Cookie. И теперь их необходимо получить из этого заголовка, создать на их основе правильный экземпляр класса CookieContainer и использовать его в дальнейших запросах.

Конечно для начала неплохо бы и проверить удачно ли прошла авторизация.

string[] cookieVal = null;
if(result.Headers["Set-Cookie"] != null)
	cookieVal = result.Headers["Set-Cookie"].Split(new char[] {','});

Stream ReceiveStream = result.GetResponseStream();
Encoding encode = Encoding.GetEncoding("utf-8");
StreamReader sr = new StreamReader( ReceiveStream, encode );
string answer = sr.ReadToEnd();
sr.Close();
result.Close();

Будем считать что все в порядке и пара имя/пароль правильные (все таки в данной статье обсуждается несколько иная тема). Теперь необходимо создать правильный экземпляр CookieContainer.

Возвращаемая в заголовке «Set-Cookie» строка содержит в себе список кук, разделенных запятыми. Каждая же кука имеет следующий формат: «(имя куки)=(значение куки);path=(путь куки);domain=(домен куки);Expires=(дата, до которой кука хранится)». Основываясь на этих данных и создается контейнер с возвращенными куками.

CookieContainer cookie = new CookieContainer();
foreach(string cook in cookieVal)
{
	string[] cookie1 = cook.Split(new char[] {';'});
	if(cookie1.Length < 2)
		continue;
	cookie.Add(new Cookie(cookie1[0].Split(new char[] {'='})[0], cookie1[0].Split(new char[] {'='})[1], 
                cookie1[1].Split(new char[] {'='})[1], cookie1.Length > 2 ? cookie1[2].Split(new char[] {'='})[1] : ""));
}

Проверка на длину массива куки вставлена из-за того, что дата в возвращаемой в Set-Cookie строке представлена в полном формате и имеет символ «,» в своем составе. Соответственно может получиться так, что некоторые элементы массива cookieVal будут содержать часть даты. А так как нас не особо интересует Expires дата - мы просто отбрасываем данные строки.

Теперь есть все для получения защищенных страниц. Для присоединения CookieContainer к экземпляру класса WebRequest используется свойство CookieContainer данного класса. Ну а дальше все, как по теории:

HttpWebRequest req1 = (HttpWebRequest) HttpWebRequest.Create((URL защищенной страницы));
req1.UserAgent = "Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+NT+5.0)";
//Вот оно - важное дополнение.
req1.CookieContainer = cookie;
req1.Method = "GET";
HttpWebResponse result1 = (HttpWebResponse) req1.GetResponse();
Stream ReceiveStream1 = result1.GetResponseStream();
StreamReader sr = new StreamReader( ReceiveStream1, encode );
string html = sr.ReadToEnd();
result1.Close();

Дело сделано :).


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=4166