A fejezet azt tekinti át, hogy hogyan lehet a szerveroldali szkriptekhez kérést intéző klienseket megkülönböztetni, és kliensenként eltérő adatot tárolni. Ennek kliens- és szerveroldali megoldásait elemezzük, majd nézzük meg, hogy PHP-ban ez hogyan kezelhető. A fejezet végén megnézzük, hogy ezzel a technológiával hogyan kezelhető a felhasználók azonosítása.
Az oldalkiszolgálás folyamatában a kliens egy HTTP kérést küld a webszervernek, amelyre az HTTP válasz formájában válaszol. A kliens és szerver közötti kapcsolat csak arra az időre él, ameddig a kérés-válasz folyamat végrehajtódik, ezt követően a kliens és szerver közötti kapcsolat megszakad. A következő kéréshez egy új kapcsolat épül fel, és ebben történik a válasz kiszolgálása. A HTTP protokoll úgy lett megtervezve, hogy a webszerver e között a két kérés között nem képes semmiféle kapcsolatot teremteni, minden kérés a többitől függetlenül kerül kiszolgálásra. Ezt hívják a HTTP protokoll állapotmentességének, ami azt jelenti, hogy a szerver nem tudja, melyik kliens fordul hozzá, és annak milyen az állapota.
Ennek az állapotmentességnek több folyománya is van. Egyrészt egyetlen kliens esetén is szükség lehet az állapot megőrzésének több kérésen keresztül. Vegyük a webáruházakban található kosár példáját. A felhasználó a termékek böngészése során több kérést is küld a szervernek, némelyik kérésben jelezve, mit szeretne a kosárba tenni. A kosár állapotát meg kell őrizni az egyes kérések között. Ahogy láttuk, az állapot megőrzését nem lehet PHP változókra bízni, mert azok minden kérés elején létrejönnek és a végén megszűnnek. Megoldást erre a problémára az előző fejezet technológiái adhatnak: a kosár tartalmát fájlba vagy adatbázisba kell menteni, és minden kérésnél onnan kiolvasni, és ha új termék jött, akkor azt beletenni és elmenteni.
Több kliens esetén azonban ez már nem jó megoldás, mert így minden kliens ugyanazon a kosáron osztozik. Az adatok ilyen jellegű tárolása tehát az alkalmazásszintű adatoknál lehet jó megoldás. Ugyanakkor már ez a webáruházas példa is jól mutatja, hogy szükség van kliensszintű tárolásra is. Mindennapi alkalmazásokban is gyakran van szükség az adatok felhasználónkénti megkülönböztetésére:
A kliensek megkülönböztetését és a kliensenkénti adattárolást munkamenet-kezelésnek hívjuk. A HTTP protokoll állapotmentességének másik következménye tehát az a kérdés, hogy hogyan tudjuk a klienseket megkülönböztetni egymástól? Hol tudjuk a kliensenkénti állapotot tárolni? Az oldalkiszolgálásban kliens és szerver vesz részt a HTTP protokoll segítségével. Mivel ezek közül a HTTP protokoll állapotmentes, ezért az állapot megtartását vagy kliens, vagy szerver oldalon oldhatjuk meg.
Ebben a fejezetben a munkamenet-kezelés lehetőségeit nézzük végig, először a kliens-, majd a szerveroldali megoldásokra koncentrálva. Ezt követően áttekintjük, hogy PHP-ban milyen beépített parancsok szolgálnak a munkamenet-kezelésre, végül pedig ehhez a témához szorosan illeszkedő tipikus műveletekkel, felhasználók regisztrálásával és bejelentkeztetésével foglalkozunk.
Az alábbiakban egy egyszerű kis példán keresztül vizsgáljuk a munkamenet-kezelés lehetőségeit.
Feladat: Tároljuk egy számláló értékét felhasználónként, és minden kérésnél növeljük a számláló értékét eggyel!
A kliensoldali munkamenet-kezelésnek az a lényege, hogy a kliensenkénti adatokat magán a kliensen tároljuk, és minden kérésnél felküldjük a szervernek őket. A feldolgozást követően a tárolandó adatokat a szerver visszaküldi a kliensnek.
A következő lehetőségek vannak a kliensoldali munkamenet-kezelésre:
Első lehetőségünk a megőrzendő adatokat az URL kérésszöveg részében feltüntetni.
Ekkor a kérés elküldésekor a feldolgozó program a kérésszöveget feldolgozva – PHP-ban a $_GET tömb segítségével – jut hozzá az adott kliensen tárolt értékhez. A válaszban pedig olyan URL-t kell generálnia, amelyben megjelenik a tárolandó érték.
Példánkban a feldolgozó szkript a következőképpen néz ki:
<?php $szamlalo = 0; if (isset($_GET['szamlalo'])) { $szamlalo = $_GET['szamlalo']; } $szamlalo += 1; var_dump($szamlalo); ?> <a href="szamlalo2.php?szamlalo=<?php echo $szamlalo; ?>">Növel</a>
Az adatok URL-ben tárolásának van néhány nyilvánvaló hátránya:
Az URL-ben történő tárolás hátrányainak egy része abból fakadt, hogy az URL nagyon szem előtt volt és limitált volt a mérete. A rejtett mezőben való tárolás ezeket a problémákat oldja meg azáltal, hogy az adatokat elrejti az avatatlan szemek elől. Ekkor a kliensenként tárolt adat egy vagy több rejtett mezőben kerül tárolásra az oldalon szükségszerűen egy űrlap keretei között.
<input type="hidden" name="szamlalo" value="4">
Az űrlap elküldésekor a rejtett mező értékét a feldolgozó szkript megkapja (PHP-ban a $_POST tömbben), majd a válaszban gondoskodnia kell a megfelelő űrlap és rejtett mező generálásáról annak érdekében, hogy a következő kérésnél is megérkezzen az adat.
Példánkban ez a következőképpen néz ki:
<?php $szamlalo = 0; if (isset($_POST['szamlalo'])) { $szamlalo = $_POST['szamlalo']; } $szamlalo += 1; var_dump($szamlalo); ?> <form action="szamlalo3.php" method="post"> <input type="hidden" name="szamlalo" value="<?php echo $szamlalo; ?>"> <input type="submit" value="Növel"> </form> <!-- Ez nem működik --> <a href="szamlalo3.php">Növel</a>
A rejtett mezőben való tárolás előnyei:
Rejtett mezők használatának azonban több hátulütője is van:
A rejtett mezős megoldás, bár megoldott néhány problémát az URL-ben való tárolással kapcsolatban, még mindig számos sebből vérzik: használata körülményes, az adat továbbra is manipulálható. Sokkal kényelmesebb azonban a sütik használata. Bár ebben a tananyagban a kliensoldali programozásnál találkoztunk velük, a sütik a HTTP protokoll részei, és mint ilyen a kliens-szerver közötti adatkommunikáció egyik formáját biztosítják, ezért kezelésük a szerveroldali technológiákhoz is kapcsolódik: onnan lekérdezhetőek és beállíthatóak értékeik.
A klienstől érkező sütik HTTP fejlécként érkeznek a következő formában:
A PHP ennek a fejlécnek az értékét automatikusan a $_COOKIE szuperglobális tömbben teszi elérhetővé.
A szerver a HTTP válasz fejlécében jelzi a kliens számára sütielhelyezési szándékát:
Ezt a fejlécet generálhatnánk a header paranccsal is, de sokkal kényelmesebb a setcookie függvény használata, mely a süti elhelyezését paraméterezési kérdéssé egyszerűsíti.
<?php //Általános formája $siker = setcookie($név[, $érték [, $expires = 0 [, $path [, $domain [, $secure = false]]]]]); //Néhány példa setcookie('alma', 'piros'); setcookie('körte', 'sárga', time() + 60); //lejárat 60 mp múlva ?>
A munkamenet-kezelés során tehát a kéréskor a böngésző elküldi az adott szerverhez tartozó sütiket, azokat a feldolgozó szkript kiolvashatja (PHP-ban a $_COOKIE tömbből). A válaszban viszont csak azokat a sütiket kell elhelyezni, amelyek értékei változnak. A többi süti a böngészőben már úgyis el van tárolva, ezeket nem kell újra leküldeni.
Számlálós példánk sütikkel a következőképpen néz ki. A mellékelt képernyőkép jól mutatja, hogy a kliensen már a számláló következő értéke van, a $_COOKIE tömb kiírása még az előző kéréshez tartozott.
<?php $szamlalo = 0; if (isset($_COOKIE['szamlalo'])) { $szamlalo = $_COOKIE['szamlalo']; } $szamlalo += 1; setcookie('szamlalo', $szamlalo); print_r($_COOKIE); ?> <a href="szamlalo.php">Növel</a>
Előnyök:
Hátrányuk:
Ugyan a fenti lehetőségek némelyike viszonylag elfogadható megoldást adott a munkamenet-kezelésre, a kliensoldali adattárolásnak általában több hátránya is van:
Ezeket a hátrányok abból fakadnak, hogy az adatok a kliensen tárolódnak. A problémákra megoldást a szerveroldali tárolás ad.
A szerveroldali adattárolás előnyei azonnal megmutatkoznak a fent említett problémákkal szemben:
A kliensoldali megoldásokban nem volt szükség a kliensek szerveroldali megkülönböztetésére, hiszen minden kliens a kéréshez csatolta azokat az adatokat, amelyek hozzá tartoztak. Ha az adatokat a szerveroldalon tároljuk, akkor viszont szükségessé válik a kliensek megkülönböztetése, hiszen az adatokat kliensenként kell tárolni. Tudnunk kell, melyik kliensről érkezik a kérés, hogy a hozzá tartozó adatokat használjuk.
A kliensek azonosítása úgy történik, hogy minden kliens kap egy egyedi kulcsot a szervertől. Ezt a kulcsot csatolja minden kéréséhez, és a szerver ez alapján veszi elő az adott kulcshoz tartozó adatokat. A kliensen tárolandó adatok helyett tehát itt egyetlen adat, maga a kulcs közlekedik, ezt kell egyedül kliensenként a kliensoldalon tárolni. Ennek tárolásához a fent említett kliensoldali megoldások közül bármelyik használható, de általában a kényelmessége miatt elsősorban sütiben szokták az azonosító kulcsot tárolni. Mivel azonban a sütik letilthatók, így másodlagos megoldásként az URL-ben tárolás jön általában szóba. Az azonosító kulcsot munkamenet-azonosítónak szokták nevezni.
Az adatok tárolása szerveroldalon bármelyik korábban említett módon, fájlban vagy adatbázisban is megvalósulhat, egyszerűsége miatt a fájlos használat elterjedtebb. Összetettebb alkalmazások hatékonysági és biztonsági szempontok alapján adatbázisban tárolják a kliensenkénti adatokat. Azt azonban látnunk kell, hogy a munkamenet-kezelés plusz erőforrásokat igényel a szerver részéről.
A munkamenet-azonosítóra nagyon kell vigyázni, hiszen ez adja a kulcsot a szerveroldali adatainkhoz. A legtöbb internetes támadás célja a munkamenet-azonosító megszerzésére irányul, hiszen ennek ismeretében könnyen hozzáférhetnek levelezésünkhöz, internetbankunkhoz, dokumentumainkhoz, stb. Éppen ezért kellő körültekintéssel kell kezelni a gyanúsnak tűnő hivatkozásokat, leveleket. Amit mindenképpen megtehetünk, hogy alkalmazásainkból mindig lépjünk ki, ezzel ugyanis munkamenetünket szüntetjük meg. Az internetes támadások részletesebb taglalása nem fér e tananyag keretei közé, az érdeklődőknek jó kiindulási alapot jelenthet a Weblabor ezzel foglalkozó cikke.
PHP-ban számos függvény és nyelvi elem segíti a munkamenet-kezelést. A PHP alapvetően elrejti a munkamenet-azonosítóval kapcsolatos teendőket: automatikusan létrehozza őket, leküldi a kliensre, megfelelő módon váltogat a süti és az URL-es tárolás között, és egyszerű módon tálalja a munkamenetben tárolt adatokat. Ezek közül a fejlesztő számára ez utóbbi a legfontosabb. A munkamenet adatait a PHP a $_SESSION szuperglobális asszociatív tömbben tárolja. A szkript kezdetekor ebbe tölti be, a szkript végén pedig ennek tartalmát menti ki (alapértelmezetten fájlba).
További hasznos függvények:
A számlálós feladat PHP-ban a következőképpen oldható meg:
<?php //Munkamenet indítása session_start(); //Az URL-ben érkező paraméterek print_r($_GET); //A betöltött munkamenet adatai print_r($_SESSION); //A számláló beolvasása a mentett munkamenetből $szamlalo = 0; if (isset($_SESSION['szamlalo'])) { $szamlalo = $_SESSION['szamlalo']; } $szamlalo += 1; //Számláló mentése a munkamenetbe $_SESSION['szamlalo'] = $szamlalo; //Számláló megjelenítése az oldalon var_dump($szamlalo); ?> <a href="szamlalo5.php?<?php echo SID; ?>">Növel</a>
Az alábbi kép azt mutatja, hogy sütik letiltása esetén hogyan jelenik meg a munkamenet-azonosító az URL-ben.
Munkamenet megszüntetésekor a $_SESSION tömböt ki kell üríteni, majd a session_destroy() paranccsal magát a munkamenetet is töröljük.
<?php //Munkamenet indítása session_start(); //Munkamenetadatok törlése $_SESSION = array(); //Munkamenet törlése session_destroy(); ?>
Webes alkalmazások gyakran előforduló jellegzetessége, hogy a felhasználót csak akkor engedjük adataihoz hozzáférni, ha valamilyen módon azonosította magát. Ezt nevezzük hitelesítésnek, amely során azt nézzük meg, ki használja az alkalmazást. Ilyen szempontból megkülönböztetünk azonosított és vendég felhasználót. Az a konkrét alkalmazás logikájától függ, hogy ezt az információt hogyan használjuk fel.
A következőkben a hitelesítési folyamat részeit tekintjük át.
Felhasználók azonosítására számos technológia alakult ki, amelyek általában függetlenek a feldolgozó szkriptektől.
Ezek a technológiák kizárólag a hitelesítésre valók, további adat tárolására nem alkalmasak.
Munkamenettel általánosabb és sokkal rugalmasabb hitelesítési megoldások alakíthatók ki. Lényege röviden annyi, hogy sikeres bejelentkezés (felhasználónév-jelszó) után a szerver a kliens munkamenetében egy speciális kulcsot helyez el. Minden további kérésnél elég megnézni e kulcsnak a jelenlétét a munkamenetben. Ha megvan, akkor a felhasználó már sikeresen teljesítette az azonosítást. Ha nincs ilyen, akkor a felhasználó még nem azonosította magát. Hétköznapi példaként a kártyás beléptető rendszerek, vagy a csuklós karszalagok állíthatók párhuzamba ezzel.
A hitelesítési folyamat a következő:
A hitelesítési folyamat első lépése a regisztráció, melynek során a rendszerbe felvisszük azonosító adatainkat. Ez tipikusan a felhasználónév vagy e-mail cím, valamint a jelszó megadásából áll egy erre szolgáló űrlapon. A feldolgozás során csak olyan azonosító adatok vihetők fel, amelyek még nem léteznek a rendszerben. A feltételek teljesülése esetén az azonosító adatokat eltároljuk.
A regisztrációs űrlap egyszerűsített formája a következő:
<?php print_r($hibak); ?> <form action="reg.php" method="post"> Felhasználónév: <input type="text" name="felhnev"> <br> Jelszó: <input type="password" name="jelszo"> <br> <input type="submit" name="reg" value="Regisztrál"> </form>
Ugyanebben a fájlban (reg.php) a feldolgozási logika a szokásos űrlapfeldolgozást tükrözi. Főbb pontjai:
<?php $hibak = array(); if ($_POST) { $felhnev = trim($_POST['felhnev']); $jelszo = $_POST['jelszo']; $jelszavak = fajlbol_betolt('jelszavak.txt'); if (strlen($felhnev) == 0) { $hibak[] = 'Nincs felhnev!'; } if (strlen($jelszo) == 0) { $hibak[] = 'Nincs jelszo!'; } if (array_key_exists($felhnev, $jelszavak)) { $hibak[] = 'Letezo felhnev!'; } if (!$hibak) { $jelszavak[$felhnev] = md5($jelszo); fajlba_ment('jelszavak.txt', $jelszavak); header('Location: login.php'); exit(); } } ?>
A bejelentkezés szokásos módja a felhasználónév vagy e-mail cím, valamint a jelszó megadása egy űrlapon. Az űrlap a következőképpen nézhet ki:
<?php print_r($hibak); ?> <form action="login.php" method="post"> Felhasználónév: <input type="text" name="felhnev"> <br> Jelszó: <input type="password" name="jelszo"> <br> <input type="submit" name="belep" value="Belép"> </form>
Ugyanebben a fájlban (login.php) a feldolgozási logika a következő:
<?php $hibak = array(); if ($_POST) { $felhnev = trim($_POST['felhnev']); $jelszo = $_POST['jelszo']; $jelszavak = fajlbol_betolt('jelszavak.txt'); if (!(array_key_exists($felhnev, $jelszavak) && $jelszavak[$felhnev] == md5($jelszo))) { $hibak[] = 'Nem jó!'; } if (!$hibak) { $_SESSION['belepve'] = true; $_SESSION['felhnev'] = $felhnev; header('Location: valahova.php'); exit(); } } ?>
A hitelesítés ellenőrzése a következő segédfüggvénnyel tehető meg. Ennek során csupán a hitelesítő kulcs (belepve) jelenlétét vizsgáljuk, értéke indifferens.
<?php session_start(); function azonositott_e() { return isset($_SESSION['belepve']); } ?>
Kijelentkezés során a munkamenetben lévő hitelesítő kulcsot kell megszüntetnünk. Gyakran ez az egész munkamenet megszüntetésével együtt történik, bár ez feladatfüggő.
<?php unset($_SESSION['belepve']); ?>
A tananyag az ELTE - PPKE informatika tananyagfejlesztési projekt (TÁMOP-4.1.2.A/1-11/1-2011-0052) keretében valósult meg.
A tananyag elkészítéséhez az ELTESCORM keretrendszert használtuk.