Введение
Эта статья является продолжением статьи Основы работы с сервером livejournal, в которой мы рассмотрели несколько видов авторизаций, которые могут быть использованы для написания клиента наподобие Semagic или LJ.NET. В этой статье мы с вами рассмотрим еще один способ авторизации на сайте livejournal.com (ЖЖ).
Рассмотренные ранее виды авторизации позволяют сделать только те операции, которые описаны в документации. Таким образом мы не сможем, например, получить доступ к подзамочным записям друзей, посколько для доступа к чужим записям (пусть даже только для чтения) нет соответствующей функции сервера по протоколу Flat или XML-RPC (по крайней мере их нет в документации). Эта статья как раз и описывает способ прочитать такие записи.
Авторизация
Если попытаться получить доступ к подзамочным записям, используя cookies, которые были получены при авторизации одним из способов, описанных в предыдущей статье, то у нас ничего не получится. Эти Cookies не дают нам прав на просмотр сообщений только для друзей. Таким образом получается, что должен быть еще какой-то способ авторизации.
Эмуляция отправки данных с формы
Давайте рассмотрим страницу авторизации на сайте ЖЖ, расположенную по адресу http://www.livejournal.com/login.bml. Если вы уже авторизованы на этом сайте в своем браузере, то нажмите кнопку "Выход" в правом верхнем углу. Итак, на этой странице неавторизованные пользователя видят две формы для входа в систему - в правом верхнем углу и слева в центре. Как ни странно, эти две формы работают по-разному. В данной статье мы рассмотрим авторизацию с помощью эмуляции отправки данных с нижней формы.
Если проследить с помощью какого-нибудь прокси-сервера, который может показывать передаваемые данные (я предпочитаю Proxomitron), что происходит при отправке данных с помощью этой формы, то мы увидим, что на сервер передаются следующие параметры:
chal |
Оклик (Challenge) сервера |
response |
Сформированный с помощью пароля ответ |
user |
Имя пользователя |
password |
Пароль. Значение этого параметра остается пустым |
lj_form_auth |
Идентификатор формы |
action:login |
Значением этого параметра является просто надпись на кнопке для отправки данных с формы |
Для авторизации требуются только первые три параметра. Параметр chal можно получить двумя способами. Можно загрузить страницу http://www.livejournal.com/login.bml и из полученного кода HTML выделить значение скрытого поля chal формы. В исходнике страницы этот параметр прописан следующим образом:
Но лучше, конечно, воспользоваться функцией getchallenge сервера с помощью протокола Flat или XML-RPC. Эту функцию мы уже использовали в предыдущей статье. Значение параметра response вычисляется точно так же, как и в методе авторизации Challenge/Response, рассмотренном ранее, а именно по формуле:
MD5(chal + MD5(password))
Все эти данные надо отправлять по адресу http://www.livejournal.com/login.bml. Если все сформировано правильно, то в ответе сервера мы получим довольно много cookies. Из всех cookies для успешной загрузки подзамочных записей нам нужны будут только cookies с именами ljloggedin (таких cookies будет две штуки с одинаковыми значениями) и ljmastersession. Именно эти cookies и надо подцеплять к следующему запросу, который и будет читать записи только для друзей. Разумеется, если останутся и другие cookie, то ничего плохого не произойдет.
Надо заметить, что при получении cookies в классе HttpWebRequest из .NET Framework необходимо установить значение параметра AllowAutoRedirect в false, иначе произойдет редирект на другую страницу и мы не сохраним cookies. Зато потом при использовании полученных cookies это значение должно быть true, так как при дальнейшей загрузке страницы с этими cookies происходит серия редиректов. Если AllowAutoRedirect будет равно false, то все эти редиректы придется отслеживать вручную.
Реализация
Для демонстрации этого метода я дополнил проект LJServerTest из предыдущей статьи возможностью читать подзамочные записи. Скачать обновленные исходники можно здесь. При загрузке страницы с указанным адресом откроется новое окно, в котором будет показана загруженная страница с помощью контрола WebBrowser.
Чтение подзамочных записей осуществляет метод GetPrivatePage() класса LJServer, который принимает три параметра: адрес загружаемой страницы, логин (имя пользователя) и пароль:
publicstring GetPrivatePage (string url, string login, string password)
{
CookieCollection cookies = GetBaseCookie (login, password);
string res = GetPage (url, cookies);
return res;
}
Здесь мы сначала получаем cookies, выдаваемые при авторизации с помощью метода GetBaseCookie, а потом загружаем страницу, используя эти cookies. Получение cookies выглядит следующим образом:
private CookieCollection GetBaseCookie
(string login,
string password
)
{
// Получаем оклик сервера (см. предыдущую статью)
string lj_login_chal = GetChallenge
();
// Рассчитаем ответ как в методе авторизации challenge / response
string auth_response = GetAuthResponse
(password, lj_login_chal
);
// Строка запроса для отправки через форму
string textRequest =
string.
Format("chal={0}&response={1}&user={2}",
HttpUtility.
UrlEncode(lj_login_chal
),
HttpUtility.
UrlEncode(auth_response
),
HttpUtility.
UrlEncode(login
));
byte[] byteArray = Encoding.
UTF8.
GetBytes(textRequest
);
// Получаем класс запроса
HttpWebRequest request =
(HttpWebRequest
)WebRequest.
Create("http://www.livejournal.com/login.bml");
// Заполняем параметры запроса
request.
Method =
"POST";
// Запрещаем автоматический редирект, чтобы сохранить cookies, полученные на первой странице после запроса
request.
AllowAutoRedirect =
false;
request.
ContentType =
"application/x-www-form-urlencoded";
request.
ContentLength = textRequest.
Length;
request.
UserAgent =
"LJServerTest";
// Очищаем коллекцию от старых cookie и добавляем туда новые
request.
CookieContainer =
new CookieContainer
();
// Заполняем параметры Proxy (_proxy == null, если прокси не используется)
request.
Proxy = _proxy;
// Отправляем данные запроса
Stream requestStream = request.
GetRequestStream();
requestStream.
Write(byteArray,
0, textRequest.
Length);
// Получаем класс ответа
HttpWebResponse response =
(HttpWebResponse
)request.
GetResponse();
// Читаем ответ
Stream responseStream = response.
GetResponseStream();
StreamReader readStream =
new StreamReader
(responseStream, Encoding.
UTF8);
string currResponse = readStream.
ReadToEnd();
readStream.
Close();
response.
Close();
// Оставим только самые необходимые cookies
CookieCollection newCollection =
new CookieCollection
();
for(int i =
0; i < response.
Cookies.
Count; i++
)
{
if(response.
Cookies[i
].
Name ==
"ljloggedin" ||
response.
Cookies[i
].
Name ==
"ljmastersession")
{
newCollection.
Add(response.
Cookies[i
]);
}
}
return newCollection;
}
Обратите внимание, на строки кода, где формируется запрос. Не смотря на то, что при методе POST передаваемые параметры разделяются с помощью разделителя строк, здесь мы используем символ `&`: "chal={0}&response={1}&user={2}". Запрос при этом будет сформирован как положено, но, если заменить амперсанды символами переводом строки, то авторизация не пройдет. В конце функции мы оставляем только cookies с именами ljloggedin и ljmastersession. Это сделано больше для демонстрации, чтобы показать, что и без других cookies все работает. В принципе, это делать не обязательно.
А для загрузки страницы с использованием cookies используется довольно простая функция:
privatestring GetPage
(string url, CookieCollection cookies
)
{
HttpWebRequest request =
(HttpWebRequest
)WebRequest.
Create(url
);
// Заполняем параметры запроса
// Здесь мы разрешаем автоматические редиректы
request.
AllowAutoRedirect =
true;
request.
Credentials = CredentialCache.
DefaultCredentials;
request.
Method =
"GET";
request.
CookieContainer =
new CookieContainer
();
if(cookies !=
null)
{
request.
CookieContainer.
Add(cookies
);
}
// Заполняем параметры Proxy (_proxy == null, если прокси не используется)
request.
Proxy = _proxy;
// Получаем класс ответа
HttpWebResponse response =
(HttpWebResponse
)request.
GetResponse();
// Читаем ответ
Stream responseStream = response.
GetResponseStream();
StreamReader readStream =
new StreamReader
(responseStream, Encoding.
UTF8);
string currResponse = readStream.
ReadToEnd();
readStream.
Close();
response.
Close();
return currResponse;
}
Заключение
Ну вот мы и рассмотрели еще один способ авторизации на сайте livejournal.com. Интересно, сколько еще разных методов авторизации таит в себе ЖЖ? :)