[C#] wysyłanie danych logowania za pomocą WebRequest

Witam, mam taki kod:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Net;

using System.IO;


namespace Aplikacja

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }


        private void Form1_Load(object sender, EventArgs e)

        {


        }


        private void button1_Click(object sender, EventArgs e)

        {

            string html;

            HttpWebRequest sessionRequest = (HttpWebRequest)WebRequest.Create("http://profil.wp.pl/login.html");

            sessionRequest.CookieContainer = new CookieContainer();

            CookieContainer cookies = sessionRequest.CookieContainer;

            WebResponse sessionResponse = sessionRequest.GetResponse();

            sessionResponse.Close();


            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://profil.wp.pl/login.html");

            request.CookieContainer = cookies; 


            request.Method = "POST";

            string vNazwaUzytkownika = "xxx";

            string vHaslo = "xxx";

            string loginData = "login=" + vNazwaUzytkownika + "&password=" + vHaslo;

            request.ContentType = "application/x-www-form-urlencoded";

            byte[] loginDataBytes = Encoding.ASCII.GetBytes(loginData);

            Stream postData = request.GetRequestStream();

            postData.Write(loginDataBytes, 0, loginDataBytes.Length);

            postData.Close();

            HttpWebResponse res = (HttpWebResponse)request.GetResponse();

            webBrowser1.Navigate("http://profil.wp.pl/login.html");

            res.Close();


            request = (HttpWebRequest)HttpWebRequest.Create("http://profil.wp.pl/login.html");

            request.CookieContainer = cookies;

            request.Method = "GET";

            res = (HttpWebResponse)request.GetResponse();

            StreamReader sr = new StreamReader(res.GetResponseStream());

            html = sr.ReadToEnd();

            res.Close();



          // richTextBox1.AppendText(html);

        }

    }

}

Chciałem, aby ten program po wpisaniu dobrych danych ( użytkownika oraz hasło), wyświetlił mi stronę po zalogowaniu ( w tym przypadku poczty na WP). Wywołuję to komendą webBrowser1.Navigate. Ale niestety efekt jest taki, że zamiast wyświetlać stony po zalogowaniu, wyświetla mi stronę gdzie trzeba się logować. Co muszę zmienić żeby zadziałało, jak należy?

Ale chaos masz w tym kodzie. Najlepiej zacznij od samego początku.

Parametry, które musisz przesłać są zupełnie inne niż te, które aktualnie wysyłasz. Musisz wysłać taki łańcuch:

_action=login&url=%2F&idu=100&serwis=&enticket=&othk=0&login_username=&login_password=&countTest=1

Najlepiej zainstaluj rozszerzenie firebug do przeglądarki firefox i popatrz, co jest przesyłane w momencie wykonywania POST-a (to tak na przyszłość).

Pierwsze żądanie (HTTP Request), które wykonujesz powinno być GET-em. W ramach tego żądania utworzone zostaną niezbędne ciasteczka (dlatego musisz utworzyć osobny obiekt klasy CookieContainer, który będzie je przechowywał).

CookieContainer cookies = new CookieContainer();


HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://profil.wp.pl/login.html");

request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1";

request.Host = "profil.wp.pl";

request.Method = "GET";

request.CookieContainer = cookies;


HttpWebResponse response = (HttpWebResponse)request.GetResponse();

response.Close();

Te ciasteczka wykorzystasz w kolejnym żądaniu, które będzie POST-em. W ramach tego żądania wyślesz wspomniany wyżej łańcuch i to chyba tyle.

Czyli muszę przypisać do string loginData ten łańcuch?

yep, może to wyglądać np. tak:

string login = "";

string password = "";

string loginData = String.Format(

"_action={0}&url={1}&idu={2}&serwis={3}&enticket={4}&othk={5}&login_username={6}&login_password={7}&countTest={8}",

"login", "%2F", "100", "wp.pl", "", "0", login, password, "1");
private void button1_Click(object sender, EventArgs e)

        {

            //GET

            CookieContainer cookies = new CookieContainer();


            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://profil.wp.pl/login.html");

            request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1";

            request.Method = "GET";

            request.CookieContainer = cookies;

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            response.Close();


            //POST

            request = (HttpWebRequest)HttpWebRequest.Create("http://profil.wp.pl/login.html");

            request.Method = "POST";

            request.CookieContainer = cookies;

            string login = "";

            string password = "";

            string loginData = String.Format("_action={0}&url={1}&idu={2}&serwis={3}&enticket={4}&othk={5}&login_username={6}&login_password={7}&countTest={8}",

                         "login", "%2F", "100", "wp.pl", "", "0", login, password, "1");

            request.ContentType = "application/x-www-form-urlencoded";

            byte[] loginDataBytes = Encoding.ASCII.GetBytes(loginData);

            Stream postData = request.GetRequestStream();

            postData.Write(loginDataBytes, 0, loginDataBytes.Length);

            postData.Close();

            response = (HttpWebResponse)request.GetResponse();

            webBrowser1.Navigate("http://profil.wp.pl/login.html");

            response.Close();



        }

No ok, zmieniłem tak jak mówiłeś. Najpierw jest GET potem POST i jak wpiszę swoje dane logowania, to i tak nie działa, bo webbrowser wyświetla mi stronę, gdzie trzeba się logować. Nadal mam coś źle?

Już jest prawie OK. Ta metoda Navigate tutaj nie działa, bo w momencie, gdy jej używasz to tak jakbyś otworzył nowe okno przeglądarki i rozpoczął nową sesję użytkownika, w której nie jesteś zalogowany. Zrób coś takiego, tj. zamień to:

webBrowser1.Navigate(“http://profil.wp.pl/login.html”);

na:

Stream stream = response.GetResponseStream();

webBrowser1.DocumentText = (new StreamReader(stream, Encoding.GetEncoding(“iso-8859-2”))).ReadToEnd();

stream.Close();

Dzięki wielkie, teraz mi działa.

Zastanawia mnie jeszcze: skąd wyczytałeś ten ciąg: _action=login&url%2F&idu=100&serwis=&enticket=&othk=0&login_username=&login_password=&countTest=1?

Właśnie zainstalowałem firebug i próbuję go odnaleźć ale nigdzie nie widzę tego ciągu. Mógłbyś mi powiedzieć, jak się to wyszukuje?

Zerknij na ten obrazek: http://i56.tinypic.com/16a5jbt.png.

Czyli otwierasz stronę http://profil.wp.pl/login.html, klikasz na przycisk zaloguj się i szukasz w zakładce Sieć firebug-a odpowiedniego POST-a.

Ten łańcuch tworzony jest w oparciu o nazwy kontrolek HTML (input) umieszczonych w wysyłanym formularzu. Dla strony http://profil.wp.pl/login.html formularz ten wygląda następująco:

Login lub adres e-mail:




Hasło: 







			poczta mini






[/code]

Mógłbyś ten swój kod przerobić tak, żeby przy pierwszym żądaniu (GET) wczytywał z pobranego kodu HTML wartości tych wybranych kontrolek i później przekazywałbyś te wartości do tworzonego ciągu. Przykładowo w kodzie formularza, który wstawiłem powyżej dla kontrolki o nazwie idu wartość jest równa 99, czyli ciąg wyglądałby następująco:

_action=login&url%2F&idu=99&serwis=&enticket=&othk=0&login_username=&login_password=&countTest=1

Ok, a czy taka metoda działa na innych serwisach?

Np. przed chwilą przerobiłem ten program tak by mógł logować się na poczcie na Onet.pl i skopiowałem ten ciąg:

m=0&ok=0&ver=1&r=&e=&p=&perm=0&x=73&y=12

pod login wpisałem swoją nazwę użytkownika a pod password hasło, ale program mimo to wyświetla mi tylko stronę logowania

Podobnie jest, jak chcę by logował na Gmailu.

private void button1_Click(object sender, EventArgs e)

        {

            //GET

            CookieContainer cookies = new CookieContainer();


            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://poczta.onet.pl/login.html");

            request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1";

            request.Method = "GET";

            request.CookieContainer = cookies;

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            response.Close();


            //POST

          // request = (HttpWebRequest)HttpWebRequest.Create("http://profil.wp.pl/login.html");

            request = (HttpWebRequest)HttpWebRequest.Create("http://poczta.onet.pl/login.html");

            request.Method = "POST";

            request.CookieContainer = cookies;

            string login = "";

            string password = "";

            string loginData = String.Format("m=0&ok=0&ver=1&r=&e={0}&p={1}&perm=0&x=62&y=21",login,password);

            request.ContentType = "application/x-www-form-urlencoded ";

            byte[] loginDataBytes = Encoding.ASCII.GetBytes(loginData);

            Stream postData = request.GetRequestStream();

            postData.Write(loginDataBytes, 0, loginDataBytes.Length);

            postData.Close();

            response = (HttpWebResponse)request.GetResponse();

            Stream stream = response.GetResponseStream();

            webBrowser1.DocumentText = (new StreamReader(stream, Encoding.GetEncoding("iso-8859-2"))).ReadToEnd();

            stream.Close();

            response.Close();



        }

Być może, żeby to zaczęło działać np. w serwisie onet-u, będzie potrzeba najpierw zrobić to o czym pisałem wcześniej, czyli wczytywać przy pierwszym żądaniu (GET) z pobranego kodu HTML wartości wybranych kontrolek i później przekazywać je do wysyłanego łańcucha. Musiałbym zagłębić się w kod strony onet-u (gmail-a), żeby precyzyjniej odpowiedzieć, ale zacznij od wprowadzenia tej poprawki (powinno zadziałać).

Możesz też zainteresować się alternatywnymi sposobami. Zauważ, że np. onet oferuje opcję “Zapamiętaj mnie na tym komputerze”. Przeanalizuj jak to jest zrobione (zapewne ciasteczko), a będziesz mógł to użyć do automatycznego logowania w swoim programie.

Możesz też np. pomyśleć nad zautomatyzowaniem przeglądarki, czyli otwierasz w kontrolce WebBrowser stronę https://poczta.onet.pl/login.html, w zdarzeniu DocumentCompleted wypełniasz login i hasło i klikasz (programowo) w przycisk, czyli robisz dokładnie to co robiłby użytkownik.

BTW nazwę użytkownika przy logowaniu do onetu wstawiłeś dla parametru “e”, a hasło dla parametru “p”?

Zrobiłem tak:

string loginData = String.Format("m=0&ok=0&ver=1&r=&e={0}&p={1}&perm=0&x=62&y=21",login,password);

Pod login i password wprowadziłem dobre dane. Nie napisałem też, że kiedy wpiszę złe dane, to wyświetla mi się pole logowania, ale dodatkowo jest komunikat, że wystąpił błąd logowania, natomiast kiedy wpiszę dobre dane, to jest pole logowania, ale bez żadnego komunikatu

OK, czyli wstawiałeś prawidłowo. No dobra, no to spróbuj ustawiać też te pozostałe parametry, a więc m,ok,ver itd. Odczytasz je z kodu HTML strony (o czym już zresztą pisałem) i zobacz, czy wtedy to ruszy. I popatrz też w kodzie strony, czy na sumbicie form-y nie jest wykonywany jakiś javascript, który modyfikuje wartości tych parametrów, bo wówczas też musiałbyś to uwzględnić.

Te parametry m, ok, ver są w kodzie HTML takie same jak w tym ciągu: http://imageshack.us/photo/my-images/600/obrazekwm.jpg/ .

Jak przeglądałem, to nie ma skryptu javascript który modyfikowałby te parametry, ale mogę się mylić.

A czy to robi jakąś różnicę? Bo wydaje mi się, że niezależnie od tego czy to będzie Get czy Post, to te wartości kontrolek się nie zmieniają

Widać się nie zrozumieliśmy. Mi chodziło o to, że w każdej nowej sesji użytkownika wartości tych kontrolek mogą być inne (i to może powodować, że kod nie działa). Wziąłem sobie to teraz na szybko sprawdziłem. Dwa razy pod rząd otworzyłem okno przeglądarki i uzyskałem takie łańcuchy:

m=0&ok=0&ver=1&r=&e=&p=&perm=0&x=105&y=9

m=0&ok=0&ver=1&r=&e=&p=&perm=0&x=49&y=9

Jak widać zmieniła się tylko wartość kontrolki o nazwie x, co nie oznacza, że inne też się nie mogą zmieniać. Zmierzam do tego, że być może nie możesz sobie przyjąć, że ten łańcuch ma zawsze taką samą postać i zmieniać w nim jedynie wartość kontrolek hasło i nazwa użytkownika. Być może na serwerze wykonywana jest jakaś walidacja i np. gdy wartość parametru x, który został do Ciebie wysłany nie zgadza się z tym co odesłałeś to wówczas logowanie kończy się niepowodzeniem.

Dodane 13.05.2011 (Pt) 21:31

W przypadku serwisu onet.pl problem leży jednak raczej w ciasteczkach. W odpowiedzi na żądanie serwer nie wysyła dyrektywy Set-Cookie, tak więc to pierwsze żądanie (GET) nic tutaj tak na prawdę nie daje. Musisz ręcznie utworzyć wymagane ciasteczka i dołączyć je do żądania (POST).