Munkamenet kezelés alapjai
Sokéves PHP levelezőlistás tapasztalatom alapján nyugodtan kijelenthetem, hogy a PHP nyelvvel ismerkedők számára az egyik legnagyobb misztérium a munkamenet kezelés. Pedig valójában egy nagyon egyszerű és gyakran nélkülözhetetlen eszközről van szó, mint ezt a következőkben reményeim szerint kiderül. Cikkem első részében a munkamenet kezelés alapjairól lesz szó, míg a következő részben a biztonság kérdését boncolgatjuk majd, megtudhatjuk, milyen fenyegetettségek léteznek a munkamenet kezeléssel kapcsolatban, és hogyan védekezhetünk ez ellen.
Ennek hatására a webszerver a PHP értelmező segítségével lefuttatja a Ezt követően a kapcsolat megszűnik a böngésző és a szerver között, majd ha az oldalon rákattintunk valamelyik linkre, akkor ugyanez a folyamat játszódik le újból. A két kérés között a webszerver képtelen bárminemű kapcsolatot teremteni, számára azok kiszolgálása egymástól teljesen függetlenül zajlik. Ezért hívják a HTTP protokollt állapotmentes protokollnak, ellentétben például az FTP protokollal, mely folyamatos kapcsolatot biztosít mindaddig, amíg valami hiba nem történik, vagy nem szakítjuk meg a kapcsolatot.
A valós életben ezzel szemben gyakorta fordulnak elő olyan helyzetek, amikor a felhasználók kényelmi, illetve alkalmazásunk funkcionális igényei megkövetelik, hogy valamilyen úton-módon a fenti működéssel ellentétben képesek legyünk kapcsolatot teremteni a látogató által megtekintett oldalak közöttHogyan továbbítsuk a
A probléma kezelésére bármilyen mechanizmus alkalmas lehet, ami biztosítja azt, hogy a kliens számára eljuttatott azonosító visszakerüljön a szerverre. Először tekintsük meg a kézenfekvő lehetőségeket, azokat a módszereket, melyeket amúgy is arra használunk, hogy adatokat jutassunk el a klienstől a szervernek, majd később érdekességképpen bemutatok egy kicsit egzotikusabb lehetőséget is:
Ismét adódik a kérdés, hogy ezen lehetőségek közül melyiket is válasszuk. Ha a biztonságosságot tartjuk szem előtt, akkor elmondható, hogy a különböző támadási módok általában alkalmazhatóak mindegyik módszer esetén, ezért érdemesebb praktikus szempontok alapján döntenünk.
Ha az azonosítót URL-ben tároljuk, akkor az könnyen kerülhet illetéktelen kezekbe, akár csak egy böngésző által küldött „REFERER” mezőben. Ezen kívül ez a tárolási mód a könyvjelzők „ellensége” is lehet. Például bejelentkezünk egy oldalra, ahol ezáltal egy új munkamenet nyitunk. Nagy nehezen megtaláljuk azt az információt, amire szükségünk van, örülünk (Vincent?), elmentjük a linket, benne a munkamenet azonosítóval. Ha a munkamenet érvényességének lejárta után próbálunk rákattintani a linkre, meglepve tapasztaljuk, hogy a kívánt tartalom helyett a bejelentkezési oldalra kerülünk.
Űrlapok használatára eleve nem mindig van lehetőségünk, így ez nem járható mód, így ha tehetjük érdemes a munkamenet azonosítót sütiben tárolni, és csak ha erre nincs lehetőségünk (kliens nem kezeli vagy csak le van tiltva) akkor használni az egyéb lehetőségeket. Sütiben való tárolás esetén például az előbb vázolt szituáció: bejelentkezünk, linket elmentjük. Ha legközelebb szükségünk van rá, akkor ismét bejelentkezünk, az új munkamenet azonosító bekerül a sütibe, majd a linkre kattintva a kéréssel együtt elküldésre kerül a szerver felé, és máris a kívánt oldalon vagyunk.
A PHP 4-es verziójának egyik nagy újdonsága volt, hogy rendelkezik saját munkamenet kezeléssel, ami ráadásul elég sokrétűen konfigurálható. Bár ezeknek egy részéről szó esik majd a későbbiekben, de mindenkinek melegen ajánlom, hogy olvassa el a PHP kézikönyv ide vonatkozó részét.
A PHP alapbeállítások mellett a
Adatok elhelyezése a munkamenetben rendkívül egyszerű: aEzenkívül lehetőségünk lehetne még használni a
A
Ezzel vége is az első résznek. A következőben, mint említettem, a munkamenet kezelés és a biztonság kérdése kerül terítékre, és már a haladók számára is fog érdekességeket rejteni.
■ A sorozatban megjelent
- Munkamenet kezelés alapjai
- Munkamenet kezelés biztonsági kérdései
Miért kell ez nekünk?
Egyből adódik a kérdés, hogy egyáltalán miért is van szükségünk munkamenet kezelésre. A kérdés megválaszolása előtt nézzük meg, hogy hogyan is zajlik a kommunikáció a böngészőnk és a programunkat futtató szerver között. Amikor megtekintünk egy weboldalt, egy párbeszéd játszódik le a két fél között, melynek a nyelve a HTTP protokoll. Ez a párbeszéd kérés/válasz formájában történik. Például ha beírjuk a böngészőnkbe, hogy http://felho.hu/cikkek.php, akkor az először kapcsolódik a felho.hu-t szolgáltató számítógép 80-as portjához és a következő kérést küldi:
GET /cikkek.php HTTP/1.1
HOST: felho.hu
cikkek.php
scriptet, majd ennek kimenetét visszaküldi a böngészőnek a következő formában:
HTTP/1.x 200 OK
Server: Apache/2.4.12 (Unix) Debian GNU/Linux PHP/6.0.0
Last-Modified: Sun, 25 Apr 2006 13:56:49 GMT
Content-Length: 177
Content-Type: text/html; charset=iso-8859-2
<html>
<head>
<title>Cikkek</title>
</head>
<body>
<a href="/cikk.php?id=1">1. cikk</a>
<a href="/cikk.php?id=2">2. cikk</a>
</body>
</html>
A valós életben ezzel szemben gyakorta fordulnak elő olyan helyzetek, amikor a felhasználók kényelmi, illetve alkalmazásunk funkcionális igényei megkövetelik, hogy valamilyen úton-módon a fenti működéssel ellentétben képesek legyünk kapcsolatot teremteni a látogató által megtekintett oldalak között
Megoldás?
Az ötlet rendkívül egyszerű: amikor a látogató először érkezik oldalunkhoz, generálunk egy azonosítót, amit eljuttatunk a klienshez oly módon, hogy azt a kliens a látogató következő oldal lekérésekor eljutassa a szerverre. Ha érkezik egy ilyen azonosító, akkor gondoskodunk róla, hogy az ismét eljusson a klienshez. Ezután egy ilyen azonosító (sessionId
) egy adott felhasználó munkamenetét fogja jelképezni, és ezen azonosító alapján a szerveren állapotinformációkat, adatokat tárolhatunk. A sessionId
tetszőleges karaktersorozatból állhat, de megfelelő generálása kulcsfontosságú lehet munkamenet kezelésünk biztonságosságának szempontjából, de erről majd később bővebben szó esik.Hogyan továbbítsuk a sessionId
-t?
A probléma kezelésére bármilyen mechanizmus alkalmas lehet, ami biztosítja azt, hogy a kliens számára eljuttatott azonosító visszakerüljön a szerverre. Először tekintsük meg a kézenfekvő lehetőségeket, azokat a módszereket, melyeket amúgy is arra használunk, hogy adatokat jutassunk el a klienstől a szervernek, majd később érdekességképpen bemutatok egy kicsit egzotikusabb lehetőséget is:sessionId
továbbítása URL-en keresztül (GET mód): az oldalunkon található minden egyes link végéhez hozzáfűzzük a munkamenet azonosítót:cikk.php?id=1&sessionId=krixkrax
sessionId
továbbítása rejtett űrlap mezőn keresztül (POST mód): az oldalunkon található minden egyes űrlapot kiegészítünk egy plusz rejtett mezővel:<form method=”post”> ...eredeti tartalom... <input type=”hidden” name=”sessionId” value=”krixkrax”> </form>
sessionId
továbbítása süti használatával (Cookie mód): a szerver az első oldal megtekintésekor beállít egy sütit, amit a böngésző minden egyes további kéréskor elküld a szervernek:
1. kérés:Válasz:GET /cikkek.html HTTP/1.1 HOST: felho.hu
2.kérés:HTTP/1.x 200 OK Server: Apache/2.4.12 (Unix) Debian GNU/Linux PHP/6.0.0 Last-Modified: Sun, 25 Apr 2006 13:57:03 GMT Set-Cookie: sessionId=krixkrax Content-Length: 177 Content-Type: text/html; charset=iso-8859-2 <html> <head> <title>Cikkek</title> </head> <body> <a href="/cikk.php?id=1">1. cikk</a> <a href="/cikk.php?id=2">2. cikk</a> </body> </html>
GET /cikkek.html HTTP/1.1 HOST: felho.hu Cookie: sessionId=krixkrax
Ismét adódik a kérdés, hogy ezen lehetőségek közül melyiket is válasszuk. Ha a biztonságosságot tartjuk szem előtt, akkor elmondható, hogy a különböző támadási módok általában alkalmazhatóak mindegyik módszer esetén, ezért érdemesebb praktikus szempontok alapján döntenünk.
Ha az azonosítót URL-ben tároljuk, akkor az könnyen kerülhet illetéktelen kezekbe, akár csak egy böngésző által küldött „REFERER” mezőben. Ezen kívül ez a tárolási mód a könyvjelzők „ellensége” is lehet. Például bejelentkezünk egy oldalra, ahol ezáltal egy új munkamenet nyitunk. Nagy nehezen megtaláljuk azt az információt, amire szükségünk van, örülünk (Vincent?), elmentjük a linket, benne a munkamenet azonosítóval. Ha a munkamenet érvényességének lejárta után próbálunk rákattintani a linkre, meglepve tapasztaljuk, hogy a kívánt tartalom helyett a bejelentkezési oldalra kerülünk.
Űrlapok használatára eleve nem mindig van lehetőségünk, így ez nem járható mód, így ha tehetjük érdemes a munkamenet azonosítót sütiben tárolni, és csak ha erre nincs lehetőségünk (kliens nem kezeli vagy csak le van tiltva) akkor használni az egyéb lehetőségeket. Sütiben való tárolás esetén például az előbb vázolt szituáció: bejelentkezünk, linket elmentjük. Ha legközelebb szükségünk van rá, akkor ismét bejelentkezünk, az új munkamenet azonosító bekerül a sütibe, majd a linkre kattintva a kéréssel együtt elküldésre kerül a szerver felé, és máris a kívánt oldalon vagyunk.
Munkamenet kezelés PHP-vel
A PHP 4-es verziója előtt a munkamenet kezelést a programozónak kellett megoldania, nem volt hozzá beépített támogatás. Mindenki saját igényeinek, lehetőségeinek függvényében írt saját munkamenet kezelőt, vagy használt egy már kész megoldást (például PHPlib).A PHP 4-es verziójának egyik nagy újdonsága volt, hogy rendelkezik saját munkamenet kezeléssel, ami ráadásul elég sokrétűen konfigurálható. Bár ezeknek egy részéről szó esik majd a későbbiekben, de mindenkinek melegen ajánlom, hogy olvassa el a PHP kézikönyv ide vonatkozó részét.
A PHP alapbeállítások mellett a
sessionId
sütiben való tárolását használja (php.ini: session.use_cookies
opció), de engedélyezhetjük az egyéb módokon történő sessionId
továbbítást is (php.ini: session.use_trans_sid
opció). Ehhez a PHP hathatós segítséget képes nyújtani, ha engedélyezzük számára, ugyanis képes a scriptek által generált HTML kód-ban a php.ini url_rewriter.tags
opciójában meghatározott HTML elemek automatikus módosítására, azokban a sessionId
elhelyezésére. Például a linkek végéhez hozzáfűzi azt, vagy formok esetén egy rejtett mezőben helyezi el (vigyázzunk, mert META elemek használatakor nekünk kell biztosítani az azonosító továbbítását). Ha mind a süti, mind az egyéb módokon történő továbbítás engedélyezve van, akkor a PHP a következők szerint jár el: ha a kéréssel érkezik sessionId
süti, akkor minden rendben, ha nem, akkor működésbe lép az azonosító különböző HTML elemekben való automatikus elhelyezése, valamint a kérésre adott válasz HTTP fejlécei közé bekerül a sessionId
süti beállító is.Munkamenet kezelés használata
Amennyiben szükségünk van munkamenet kezelésre, csak annyit kell tegyünk, hogy a programunkban kiadjuk asession_start()
parancsot, majd ezt követően lehetőségünk van munkamenet adatok tárolására. Ezzel kapcsolatban egyetlen dologra kell figyeljünk: a parancs kiadását megelőzően a programunk nem generálhat kimenetet (kivéve output buffering használata), ugyanis a sessionId
süti beállítása a Set-Cookie
HTTP fejléc használatával történik, azonban amint programunk kimenetet generál, erre már nincs lehetőség.Adatok elhelyezése a munkamenetben rendkívül egyszerű: a
session_start()
parancsot követően létrejön a $_SESSION
nevű super global tömb, ami egyrészt tartalmazza a munkamenet során már korábban elhelyezett adatokat, valamint újabbakat tehetünk bele.
<?php //szamlalo.php
session_start();
if (!isset($_SESSION[’szamlalo’])) {
$_SESSION[’szamlalo’] = 0;
}
$_SESSION[’szamlalo’]++;
echo $_SESSION[’szamlalo’];
// változó törlése
if ($_SESSION[’szamlalo’] == 20) {
unset($_SESSION[’szamlalo’]);
}
?>
session_register()
, session_is_registered()
illetve a session_unregister()
függvényeket (változó beállítására, meglétének ellenőrzésére, illetve törlésére), de ezek használata egyrészt elavult, másrészt szükséges hozzájuk a PHP register_globals
beállításának On
-ra állítása, amit jóérzésű PHP programozó úgyse tenne. Itt jegyezném meg, hogy vigyázzunk, függvények használata esetén még véletlenül se használjuk a global
kulcsszót a $_SESSION
tömbbel, mert különben bár értékadásaink látszólag érvényre jutnak, nem kerülnek tárolásra.A
$_SESSION
tömben lévő adatokat a PHP alapbeállítás szerint a session.save_path
opció által meghatározott könyvtárban tárolja: a sessionId
értékének megfelelő nevű file-ba kerül a $_SESSION
tömb serializált formája. Ez nem túl biztonságos megoldás, ezért ha a használt szerveren más felhasználók is vannak, célszerű egy külön könyvtárat megadni erre a célra, melyhez másnak nincs hozzáférése. Ebben az esetben is tartsuk szem előtt, hogy ezek a fájlok a webszerver jogosultságával jönnek létre, így bármely a szerveren futó más által írt, a webszerver által futtatott program képes ezek olvasására. Ha szükséges nagyobb biztonság biztosítása, akkor a PHP lehetőséget biztosít a munkamenet adatok tárolási mechanizmusának átdefiniálására. Segítségével például az érintett adatokat tárolhatjuk adatbázisban is, aminek a fokozott biztonság mellett másik előnye, hogy alakalmas lehet elosztott környezetben történő transzparens munkamenet kezelés megvalósítására. Erre a PHP kézikönyvben találunk példát, ezért ezzel kapcsolatban csak egy dolgot emelnék ki, ha adatbázis alapú munkamenet kezelés mellett döntünk, akkor a programunk befejezésekor hívjuk meg a session_write_close()
függvényt, ezzel biztosan elkerüljük, hogy a munkamenet adatok még azelőtt mentésre kerülnek, mire a következő oldal kiszolgálása során a programunk újból használni szeretné azokat, ami az adatok inkonzisztens állapotba való kerülését eredményezheti.Egy érdekes megközelítés
Cikkem első részének végén pedig következzen az ígért nem triviális lehetőség munkamenet kezelésre, felhasználók nyomon követésére. Mint az elején szó volt róla, bármely olyan mechanizmus jó lehet számunkra, amely biztosítja hogy egy a szerver által generált azonosítót a kliens visszaküldjön a következő kérés alkalmával. A bemutatandó módszer a HTTP protokollcache-control
fejléceit használja e cél megvalósítása érdekében. Ezek arra lennének hivatottak, hogy a böngésző meg tudja kérdezni a szervertől, hogy egy adott dokumentum megváltozott-e a szerveren, annak eldöntésére, hogy lekérje-e újból a szerverről, vagy használhatja a cache-ben tárolt verziót. Ezek a fejlécek a Last-Modified
és az Etag
, az előbbi elvileg a dokumentum utolsó változásának dátumát tárolja, míg az utóbbi egy tetszőleges szöveget. A kettő közül inkább az első támogatott a kliensek széles körében. Amikor először nézünk meg egy oldalt, a szerver elküldi ezeket a fejléceket a dokumentummal együtt, amit a böngésző eltárol a saját cache-ben. Ha újból meglátogatjuk az oldalt, akkor a böngésző (ha a felhasználó ezt nem írja elő másképp) mielőtt újból lekéri a teljes dokumentumot, elküldi ezen fejléceket a szervernek, ami ezen adatok alapján eldönti, hogy a dokumentum változott-e, és jelzi a böngészőnek, hogy az általa tárolt változat friss-e. Ennek a módszernek munkamenet kezelésre való használatát azonban több tényező nehezíti: felhasználói beállítás, kliens és szerver közötti esetleges proxy cache beállítás; de kitűnően alkalmas lehet a felhasználók webezési szokásainak titokban(!) történő megfigyelésére. További információk: Google :-).Ezzel vége is az első résznek. A következőben, mint említettem, a munkamenet kezelés és a biztonság kérdése kerül terítékre, és már a haladók számára is fog érdekességeket rejteni.
eszrevetel, kerdes?
if( ! isset( $_SESSION[’szamlalo’] ) )
inkabb igy:
if( ! array_key_exists( "szamlalo", $_SESSION ) )
igaz ez?
ha igen miert, ha nem miert?
// tudom hogy phplistan volt rola szo, de nem mindenki olvassa a listat aki esetleg olvassa az oldalt.
--
üdv: kmm...
Sebesség?
Különben le lehet mérni :)
Ez: [code]if( ! isset( $_SES
if( ! isset( $_SESSION['szamlalo'] ) )
kétszer gyorsabb, mint ez:
if( ! array_key_exists( "szamlalo", $_SESSION ) )
Lehet, hogy csak elgépelted, de vigyázz, mert a ’ nagyon nem ugyanaz, mint a ' . Tehát ez (te ezt írtad):
if( ! isset( $_SESSION[’szamlalo’] ) )
lassabb, mint a fenti kettő összesen. :)
isset - array_key_exists
Jelen esetben nincs semmi ertelme nem isset-et használni. És pont mivel egy nyelvi elem, szinte biztos, hogy gyorsabb is, mint egy függvényhívás. Az array_key_exists-nek akkor van létjogosultsága, ha olyan tömbindex létezeset szeretned vizsgalni, aminek az erteke null. Ilyenkor az isset false-t add vissza akkor is ha létezik ilyen indexu tombelem. Igaz, hogy ez talan kicsit furcsa mukodes, hiszen a null-t adni ertekul egy valtozonak, az nagyjabol egyenerteku az unset-tel, de ezek szerint nem teljesen.
Felho
Felho
Content-Length??
Content-Length: 177
Meglepne ha ilyet látnék egy php fájl elküldése előtt. Jó, persze simán lehetséges, de nem a legnormálisabb dolog. Amikor a PHP script indul még nem tudni, hogy mekkora lesz a kimenete.
Generált válaszban vannak
Ha nem érted, mutatok egy p
Hány bájt lesz? <? echo date("s")%2==0 ? "Huszonöt!" : "19!";?>
Először ki kell írnia, hogy "Hány bájt lesz? ". Mielőtt ezt elküldené, el kell küldenie a headert. De ha most elküldi a headert, még nem tudja, hogy mennyi lesz a Content-Length. Így érthető?
Ha nem hiszed, akkor tegyél a szerver gyökerébe egy cikkek.php fájlt, azzal a tartalommal, ami a példában van és küld el a "lekérdezést", ami a példában van. (Nem az én példám, hanem ami a cikkben van.) Ráadásul az még nem is lesz olyan valós, mert a cikkek.php-ben valószínűleg dinamikusan van generálva a cikklista, tehát még valószínűtlenebb, hogy a méret meglesz előre.
Szerver beállítás
Gondoltam rá, hogy tegyek eg
Megoldható, hogy buffereljen az apache vagy a php, de nem túl áltanos. Mindenesetre nem ér az az egy sor már rég ennyit. :)
Nyomtatás?
Mert nem találom...
(Pedig még regisztráltami is magam.)
Sajnos még nincs...
-boogie-