Trvalé přihlášení
Školení, která pořádám
Session proměnné v PHP, které se obvykle používají pro uložení informace o přihlášení uživatele, mají bohužel ve výchozím nastavení krátkou platnost (24 minut nebo do zavření prohlížeče). Tuto platnost můžeme prodloužit konfiguračními direktivami session.cookie_lifetime
a session.gc_maxlifetime
, ta ale nejde nastavit selektivně. Pro zajištění trvalého přihlášení je tedy možné uložit si informaci o přihlášení jinam (nejspíš do databáze) a uživateli nastavit samostatnou cookie s libovolnou platností.
Při návrhu databáze musíme počítat s tím, že jeden uživatel se může trvale přihlásit i víckrát (třeba z různých počítačů) a může chtít odhlásit jen jednu instanci. Data tedy uložíme do tabulky uzivatele_prihlaseni(id_uzivatel, token)
. Kód je potom poměrně jednoduchý:
<?php
// prvotní přihlášení
$token = md5(uniqid(mt_rand(), true));
setcookie("trvale_prihlaseni", "$_SESSION[id_uzivatel]:$token", strtotime("+1 month"));
mysql_query("
INSERT INTO uzivatele_prihlaseni (id_uzivatel, token)
VALUES ($_SESSION[id_uzivatel], '$token')
");
// automatické přihlášení
list($id_uzivatel, $token) = explode(":", $_COOKIE["trvale_prihlaseni"], 2);
mysql_result(mysql_query("
SELECT COUNT(*)
FROM uzivatele_prihlaseni
WHERE id_uzivatel = " . intval($id_uzivatel) . "
AND token = '" . mysql_real_escape_string($token) . "'
"), 0);
// odhlášení
setcookie("trvale_prihlaseni", "", 1);
list($id_uzivatel, $token) = explode(":", $_COOKIE["trvale_prihlaseni"], 2);
mysql_query("
DELETE FROM uzivatele_prihlaseni
WHERE id_uzivatel = " . intval($id_uzivatel) . "
AND token = '" . mysql_real_escape_string($token) . "'
");
?>
Uživatele identifikujeme pomocí náhodného řetězce a do cookie pro jistotu ukládáme i jeho ID. Pokud máme tu možnost, je lepší cookie nastavovat jen pro protokol HTTPS. Uživateli je vhodné nabídnout i možnost zrušit všechna trvalá přihlášení pro případ, že se zapomene odhlásit na počítači, ke kterému už nemá přístup.
Přijďte si o tomto tématu popovídat na školení Bezpečnost PHP aplikací.
Diskuse
Tomu sa vraví presné trafenie témy, práve toto budem potrebovať. Pre seba som používal jednoduchšie riešenie, teraz vidím jeho slabiny.
P.S.: oprav si preklepy "nebo nebo" a "zapomene přihlásit" -> "zapomene odhlásit"
fos4:
Lze nejak resit prihlaseni po vice ucty v jednom prohlizeci ? Asi jen pomoci sess_id v url, nepletu se ?
Jakub Vrána :
Nemusí to být zrovna sess_id, mohlo by to být třeba i user_id, ale osobně bych to neřešil. Když něco takového sám potřebuji, tak si prohlížeč spustím s druhým profilem.
v6ak:
Přesně tak, proč zhoršovat zbytku uživatelů komfort a komplikovat si to session id nebo user id v URL (dobře, v Nette by to bylo snadné) a případnými bezpečnostními problémy (dobře, u user id by to tak nevadilo).
fos4:
Byla to spise recnicka otazka jak tento problem vyresit, aby prihlaseni pod vice ucty fungovalo v ruznych tabech. Je jasne ze cookie pouzit nejde takze jedine co zbyva je predavat token v URL.
Bezpecnost v pripade predavani v URL pokulhava, ale i s cookie to neni moc slavne, vetsinou se zapomene na pouziti HTTP only a pak jsou dostupne napr. z javascriptu.
fos4:
Vlastne i zde by se hodilo pridat HTTP only, pro pripad uspesneho XSS.
v6ak:
To není samo o sobě bezpečnostní chybou. Horší je zapomenout secure u SSL, to už bezpečnostní chyba je.
Tomas:
Pri uspesnom automatickom prihlaseni by to este chcelo predlzit platnost cookie, ale to je snad samozrejmost.
Jakub Vrána :
Nejsem si jist. To, že trvalé přihlášení vydrží třeba jen měsíc, může být vlastnost, nikoliv chyba.
Tomáš Müller:
Pozor ovšem na to, že explicitně stanovená platnost cookie (tj. jiná než do zavření okna browseru) je závislá na klientském čase. Onehdá jsem si kvůli tomu docela naběhl (platnost cookie 14 dní, některým uživatelům ale nic nefungovalo...) a problém byl v tom, že uživatelé měli nastavený čas třeba i o dva roky mimo.
Jakub Vrána :
Logicky by to přitom mělo fungovat tak, že si prohlížeč spočte relativní platnost podle hlavičky Date.
Milan Kryl:
Je dobré pak uživatelům nabídnout i možnost odhlášení ze všech přihlášených instancí (například Gmail tohle umožňuje v kombinaci se zobrazením IP adresy u jednotlivých "tokenů").
Jakub Vrána :
Ano, přesně na tohle upozorňuji i v článku: "Uživateli je vhodné nabídnout i možnost zrušit všechna trvalá přihlášení".
Michal:
Proč zapisovat informace o přihlášení i do databáze? Logicky stačí ukládat je do cookies (např. společně s hashem hesla a ID uživatele) a ověřovat je přímo proti uživatelskému účtu.
Jakub Vrána :
Z bezpečnostního hlediska není vhodné do cookie ukládat tak střeženou informaci jako je heslo uživatele. S hashem hesla si moc nepomůžeme, protože pokud se tento hash dá použít pro přihlášení, tak má vlastně váhu hesla (útočníkovi stačí zjistit tento hash, aby se mohl kdykoliv přihlásit).
Proto se vedle hesla zavádí ještě token pro trvalé přihlášení. Pokud dojde k jeho kompromitaci, tak ho stačí smazat a heslo uživatele zůstane bez úhony.
tomáš:
A může tedy někdo ukrást mojí cookie s id a tokenem a pomocí toho se přihlásit? Vypadá to totiž jednoduší než práce se sessions. Tak mě zajímá, jestli to je i bezpečnejší. děkuju
Vojtěch Vondra:
Teoreticky může, musel by ale uhádnout token, což právě díky hashy času příhlášení není tak jednoduché.
Je vhodné zakomponovat do toho hashe nějaký veřejně neznámý údaj z uživatelova účtu pro dodatečné ověření.
I když uniqid(), které je jiné každou tisícinu vteřiny také stačí.
Jakub Vrána :
Dodám, že samotný hash času by nestačil, protože čas se dá docela přesně uhodnout z platnosti cookie. Důležitá je nějaká náhodná hodnota.
tqs:
Souhlasím, že Vás způsob je bezpečnější.
Ale zeptám se jinak: povolení trvalého přihlášení je samo o sobě nebezpečné (uživatel povolí trvalé přihlášení a neuvědomí si, že stejný počítač používá ještě někdo jiný). Potom mi přijde další riziko v podobě ukládání hashe hesla přímo do cookie jako zanedbatelné. Nebo se mýlím a něco zásadního jsem přehlédl?
Jakub Vrána :
Token se dá zneužít jednorázově, uživatel ho navíc může zneplatnit i z jiného přihlášení. Uložené heslo (nebo jeho hash, to vyjde na stejno) lze zneužít trvale.
v6ak:
Je to celkově zvláštní řešení. Musíš navíc řešit například funkčnost přihlášení po změně hesla.
Navíc k přihlášení může být více cest - nejen jméno + heslo, ale třeba i OpenID. Pak to tu trošku kulhá.
Tomáš Fejfar:
Přihlášen/Nepřihlášen by mělo být oddělené od autorizačního procesu. Takže jestli se uživatel loguje OpenID, jeménem nebo třeba přes LiveID je úplně jedno IMO. Pseudokod:
<?php if(hesloSouhlasi())
jmeno = ziskejIdUzivatele(jmeno, md5(heslo))
prihlasUzivatele(jmeno);
if(prostredniUkazkovyKodNahore())
prihlasUzivatele(jmenoZCookie)
?>
v6ak:
Až na toto:
* nelze změnit hash v průběhu (začal jsem o tom psát článek)
* kód je celkově zvláštní a možná by si zasloužil ještě jednou přečíst
Bendík:
Moc díky za kód. Velmi mi pomohl.
phebix:
Pěkný kód, akorát je tu malý problémek, a to pokud někomu vyprší trvalé přihlášení - cookies se po té době smažou ale co záznamy v databázi? Ty nic nesmaže a počet takovýchto záznamu bude v databázi narůstat.
Uvažoval jsem nad tím ukládat do databáze čas poslední použití. A pak jednou začas vymazat záznamy jejichž doba od posledního použití bude větší jak doba platnosti cookies.
Petr Kadeřábek:
Dobrý den,
Naučil jsem se nastavovat platnost session tím nejjednodušším způsobem, tedy - session_set_cookie_params(doba platnosti);
session_start();
tento kod uložím do samostatného souboru a poté ho vnořým do každého skriptu.
Nyní jsem chtěl, ale nabídnout uživateli volbu - zda chce zůstat přihlášen - či ne, ale nevím jak to udělat
do přihlašovacího formuláře dám checkbox (name login) - poté bych ověřil $_POST['login'] - pokud existuje nastavil bych platnost, když né nechám po zavření "zničit" relaci.
Ale řeším 2 problémy:
1) jak informovat ostatní skripty, jakou volbu uživatel zaškrtl - napadlo mě že vytvořím $_SESSION['login'] a tam vložím hodnotu podle toho, co uživatel zaškrtl
tuto hodnotu poté ověřím a podle toho nastavím platnost
Ale nevím jestli je to tak dobře? (1. otázka)
2) nevím jak to správně nakodovat, abych mohl mít před session_start() atd. jiný kod - tedy podmínku - if..else
(2. otázka)
Mohl by jste mi někdo poradit, nejlépe by bylo, kdyby jste napsali příklad, nebo samozřejmě jiné řešení, ale budu vděčný za každou pomoc
Jakub Vrána :
Před session_start() může být libovolný kód, jen nesmí nic vypisovat.
Nastavení platnosti podle uživatele se dá udělat takhle:
<?php
session_start();
session_write_close();
session_set_cookie_params(); // V závislosti na volbě uživatele.
session_start();
?>
David:
Nemali by byt tieto dva riadky obracene? Najskor zistit udaje z aktualnej cookie a pak tuto cookie smazat.
<?php
// odhlášení
setcookie("trvale_prihlaseni", "", 1);
list($id_uzivatel, $token) = explode(":", $_COOKIE["trvale_prihlaseni"], 2);
?>
Jakub Vrána :
$_COOKIE se nastavuje na začátku stránky, volání setcookie() to neovlivňuje.
David:
ok diky, ale pre istotu som to dal v obratenom poradi, dufam ze to nicomu neuskodi :)
Diskuse je zrušena z důvodu spamu.