CMYK NA CD NEWSY Z OKŁADKI FIRMA MAGAZYN PROGRAMY WARSZTAT PHP i bezpieczne logowanie PHP Każdy programista PHP prędzej czy póxniej stanie przed problemem stworzenia systemu logowania do serwisu internetowego. DziS internauci są już przyzwyczajeni do tego, że coraz częSciej niektóre interesujące ich informacje dostępne są jedynie po zarejestrowaniu się w serwisie oraz zalogowaniu się. Często potrzeba logowania wynika także z koniecznoSci zapewnienia bezpieczeństwa, np. panel administracyjny sklepu internetowego lub systemu CMS powinien być dostępny jedynie dla SciSle okreSlonego grona użytkowników. Marcin Staniszczak stnieje przynajmniej kilka podejSć do tematu autoryzacji. Najprost- lastlogin INT NOT NULL, sze z nich to wykorzystanie autoryzacji HTTP (co opisano dokład- lasttry INT NOT NULL, Inie w MI 9/04, str. 94). Jednak sposób ten jest mało elegancki, trycount INT NOT NULL, a dodatkowo często niedostępny ze względu na koniecznoSć utworze- authority INT NOT NULL, nia oraz modyfikowania pliku .htaccess. W artykule zaproponuję im- plementację klasy autoryzacji opartej o bazę MySQL oraz przedstawię UNIQUE(login), sposób jej wykorzystania z użyciem sesji, a na koniec podsunę kilka PRIMARY KEY(iduser) pomysłów na rozbudowę zaprezentowanej tu klasy. ); Przygotowania iduser unikatowy identyfikator użytkownika, Zanim zaczniemy pisanie naszej klasy, musimy się zastanowić, jakie login, passwd, name, surename, email odpowiednio: login informacje chcemy przechowywać w bazie. Już na samym początku użytkownika, jego hasło, imię, nazwisko oraz e-mail, nasuwa się mySl o loginie i haSle. Ale nie musimy się ograniczać tylko authority poziom uprawnień użytkownika. do tych dwóch informacji. W konstruowanej tu klasie będziemy prze- chowywali imię i nazwisko użytkownika, jego adres e-mail. Dodatko- Nic nie wiadomo jeszcze o polach lastlogin, lasttry oraz trycount. wo zapiszemy poziom uprawnień, co pozwoli na podział serwisu opar- Pierwsze z nich będzie służyło do przechowywania daty ostatniego lo- tego na tej klasie na moduły, do których dostęp będą mieli jedynie gowania. Dlaczego w takim razie lasttry jest typu INT? Odpowiedx użytkownicy z odpowiednimi uprawnieniami. jest prosta data będzie przechowywana w bazie w postaci uniksowe- A oto polecenie SQL tworzące tabelę używaną w naszej klasie: go znacznika czasu, czyli w postaci liczby będącej iloScią sekund jakie upłynęły od 1 stycznia 1970 roku. O znaczeniu pozostałych pól CREATE TABLE login (lasttry, trycount) napiszę podczas analizy kodu klasy autoryzacji. ( iduser INT NOT NULL auto_increment, login varchar(25) NOT NULL, Klasa cLogin passwd varchar(32) NOT NULL, Pora na przyjrzenie się klasie odpowiedzialnej za przeprowadzanie au- toryzacji. Na początku zabezpieczamy plik z naszą klasą przed wpisa- name varchar(30) NOT NULL, niem go w adresie przeglądarki. JeSli ktoS chciałby to uczynić (i wpisać surename varchar(50) NOT NULL, w przeglądarce stronę o adresie np. http://adres_strony/include/login.class.php, zo- email varchar(200) NOT NULL, stanie przekierowany na stronę główną serwisu). Nie możemy zapo- mnieć o zmianie w razie koniecznoSci adresu, na który ma zostać prze- Omawiane w artykule przykłady są dostępne na dołączonej płycie CD w katalogu Warsztat_logowanie (archiwum source.rar). Znajduje się tam m.in. plik makedb.php, 98 INTERNET.grudzień.2004 który zakłada niezbędną bazę i tabele do funkcjonowania przykładów. CMYK WARSZTAT PROGRAMY MAGAZYN FIRMA Z OKŁADKI NEWSY NA CD PHP kierowany taki użytkownik (w linii Header należy zmienić wpis Już na początku zabezpieczamy się przed przemyceniem przez ../index.php). użytkownika instrukcji SQL, które mogłyby narobić bałaganu w bazie (w najgorszym razie nawet ją usunąć). Służy do tego if (eregi( login.class.php ,$_SERVER[ PHP_SELF ])) { parametr oraz modyfikuje go tak, aby był w pełni bezpieczny Header( Location: ../index.php ); i mógł zostać wykorzystany w zapytaniu SQL. Podczas zabezpie- die(); czania hasła jest ono dodatkowo szyfrowane za pomocą funkcji } haszującej md5(..). Funkcja ta zwraca ciąg 32 znaków, które iden- tyfikują podane hasło. Jednak na podstawie znaków wygenerowa- Następnie definiujemy trzy stałe. Takie wartoSci będzie zwracała nych za jej pomocą nie da się odzyskać hasła. Z tego powodu nasza funkcja autoryzująca: w razie utraty hasła przez użytkownika, musimy np. wysłać mu AUTH_FALSE oznacza, że podano nieprawidłowy login lub nowe hasło (ponieważ to w bazie jest zapisane w postaci zakodo- hasło, wanej, co jest jednoznaczne z niemożnoScią jego odzyskania). AUTH_OK oznacza, że użytkownik istnieje i podał poprawne Takie podejScie wydaje się być rozsądne wielu użytkowników hasło, używa jednego hasła we wszelkich możliwych sytuacjach. AUTH_3TIMES_ERROR oznacza, że użytkownik o podanym Umieszczając w bazie hasło w postaci zakodowanej poprzez funk- loginie istnieje, jednak trzy razy pod rząd podał niepoprawne cję md5(..), mamy pewnoSć, że w razie włamania się kogoS do na- hasło. szej bazy danych osoba ta i tak nie pozna (w szybki sposób) haseł naszych użytkowników. define( AUTH_3TIMES_ERROR , 32); define( AUTH_FALSE , 16); function check($login, $passwd) { define( AUTH_OK , 8); $login = mysql_escape_string($login); $passwd = mysql_escape_string(md5($passwd)); Przyszła kolej na definicję klasy. Nazwą klasy będzie cLogin. Po- czątkowa literka c oznacza, że jest to klasa (przy dużych projektach ta- Gdy zmienne przechowujące hasło i login mają już postać pozwala- kie nazewnictwo ma ułatwić pracę, ale wszystko zależy od przyjętych jącą bezpiecznie je wykorzystać w zapytaniu SQL, wydobywamy z ba- wzorców). zy informację o użytkowniku, który taki login posiada. Nie zwracamy Na początku definiujemy zmienne, w których będą przechowywa- uwagi na to, czy podane hasło pasuje do tego znajdującego się w ba- ne: identyfikator połączenia z bazą MySQL ($dbConnection), znacz- zie, ponieważ po trzech nieudanych próbach logowania się na okreSlo- nik informujący o tym czy użytkownik przeszedł autoryzację ne konto chcemy je zablokować na np. 5 minut. ($isLogin), informacje o użytkowniku ($id, $name, $surename, Następnie sprawdzamy ilu użytkowników w naszej bazie ma $eMail, $lastLogin, $authority) oraz trzy zmienne, których znaczenie login taki sam, jak podany w parametrze wejSciowym funkcji poznamy nieco dalej. check(..). JeSli liczba takich użytkowników jest różna od jedynki, wówczas uznajemy, że autoryzacja nie powiodła się (ze względu na class cLogin { ograniczenie UNIQUE założone w tabeli login na polu login może var $dbConnection; istnieć tylko jeden użytkownik o tym samym loginie), w przeciw- var $isLogin; nym razie możemy przystąpić do kolejnych testów. W tym celu var $id; odczytujemy kilka informacji o logującym się użytkowniku: jego id, var $name; liczbę nieudanych prób logowania oraz datę ostatniej nieudanej pró- var $surename; by logowania. var $eMail; var $lastLogin; $record = mysql_query( SELECT * FROM login WHERE var $authority; login= $login , $this->dbConnection) var $lastTry; or die( Problem z baza danych ); var $tryCount; var $nextTry; if(mysql_num_rows($record)===1) { $res = mysql_fetch_assoc($record); Konstruktor klasy cLogin jest bardzo prosty. Przyjmuje on jeden $this->id = $res[ iduser ]; argument, którym jest identyfikator połączenia z bazą danych (klasa $this->tryCount = $res[ trycount ]; sama nie nawiązuje takiego połączenia), przekazuje ten identyfikator $this->lastTry = $res[ lasttry ]; do zmiennej $dbConnection oraz ustawia początkowe wartoSci zmien- nych $isLogin oraz $nextTry. Sprawdzamy czy hasło podane jako parametr funkcji jest iden- tyczne z tym, które znajduje się w bazie. Sprawdzamy również czy function cLogin($dbConnection) { liczba prób logowania na sprawdzane konto nie osiągnęła już licz- $this->dbConnection = $dbConnection; by 3. JeSli hasło jest niepoprawne lub nastąpiła trzecia nieudana $this->isLogin = false; próba logowania na konto, zwracamy wartoSć AUTH_3TI- $this->nextTry = false; MES_ERROR (zobacz dalej). W przeciwnym razie przypisujemy } zmiennym $name, $surename, $authority oraz $eMail poprawne wartoSci (odczytane wczeSniej z bazy danych). Następnie aktuali- Najważniejszą funkcją całej klasy jest check(..). Przyjmuje zujemy informację w tabeli. Ustalamy odpowiednią wartoSć pola ona dwa parametry: login użytkownika oraz jego hasło. Ze lastlogin oraz zerujemy pola trycoun i lasttry. Zmiennej $isLogin względu na długoSć oraz złożonoSć tej funkcji zostanie ona opi- można przypisać wartoSć true i zakończyć funkcję check(..) z war- sana fragmentami. toScią AUTH_OK. INTERNET.grudzień.2004 99 CMYK NA CD NEWSY Z OKŁADKI FIRMA MAGAZYN PROGRAMY WARSZTAT PHP if((strcmp($res[ passwd ], $passwd)===0) && if(($this->tryCount>=3) && (($this->lastTry+600)