Přihlašování uživatelů (Autentizace)
Pomalu žádná webová aplikace se neobejde bez mechanismu přihlašování uživatelů a ověřování uživatelských oprávnění. V této kapitole si povíme o:
- přihlašování a odhlašování uživatelů
- vlastních autentikátorech
V příkladech budeme používat objekt třídy Nette\Security\User, který představuje aktuálního
uživatele a ke kterému se dostanete tak, že si jej necháte předat pomocí dependency injection. V presenterech stačí jen zavolat
$user = $this->getUser()
.
Autentizace
Autentizací se rozumí přihlašování uživatelů, tedy proces, při kterém se ověřuje, zda je uživatel opravdu
tím, za koho se vydává. Obvykle se prokazuje uživatelským jménem a heslem. Ověření provede tzv. autentikátor. Pokud přihlášení selže, vyhodí se
Nette\Security\AuthenticationException
.
Tímto způsobem uživatele odhlásíte:
A zjištění, že je přihlášen:
Velmi jednoduché, viďte? A všechny bezpečnostní aspekty řeší Nette za vás.
V presenterech můžete ověřit přihlášení v metodě startup()
a nepřihlášeného uživatele
přesměrovat na přihlašovací stránku.
Expirace
Přihlášení uživatele expiruje společně s expirací úložiště,
kterým je obvykle session (viz nastavení expirace session). Lze ale nastavit
i kratší časový interval, po jehož uplynutí dojde k odhlášení uživatele. K tomu slouží metoda
setExpiration()
, která se volá před login()
. Jako parametr uveďte řetězec
s relativním časem:
Jestli byl uživatel odhlášen z důvodu vypršení časového intervalu prozradí metoda
$user->getLogoutReason()
, která vrací buď konstantu Nette\Security\UserStorage::LogoutInactivity
(vypršel časový limit) nebo UserStorage::LogoutManual
(odhlášen metodou logout()
).
Autentikátor
Jde o objekt, který ověřuje přihlašovací údaje, tedy zpravidla jméno a heslo. Triviální podobou je třída Nette\Security\SimpleAuthenticator, kterou můžeme nadefinovat v konfiguraci:
Toto řešení je vhodné spíš pro testovací účely. Ukážeme si, jak vytvořit autentikátor, který bude ověřovat přihlašovací údaje oproti databázové tabulce.
Autentikátor je objekt implementující rozhraní Nette\Security\Authenticator s metodou
authenticate()
. Jejím úkolem je buď vrátit tzv. identitu nebo vyhodit výjimku
Nette\Security\AuthenticationException
. Bylo by možné u ní ještě uvést chybový kód k jemnějšímu
rozlišení vzniklé situace: Authenticator::IdentityNotFound
a Authenticator::InvalidCredential
.
Třída MyAuthenticator komunikuje s databází prostřednictvím Nette
Database Explorer a pracuje s tabulkou users
, kde je v sloupci username
přihlašovací jméno
uživatele a ve sloupci password
otisk hesla. Po ověření jména a hesla vrací
identitu, která nese ID uživatele, jeho roli (sloupec role
v tabulce), o které si více řekneme později, a pole s dalšími daty (v našem případě uživatelské jméno).
Autentikátor ještě přidáme do konfigurace jako službu DI kontejneru:
Události $onLoggedIn, $onLoggedOut
Objekt Nette\Security\User
má události
$onLoggedIn
a $onLoggedOut
, můžete tedy přidat callbacky, které se vyvolají po úspěšném
přihlášení resp. po odhlášení uživatele.
Identita
Identita představuje soubor informací o uživateli, který vrací autentikátor a který se následně uchovává v session
a získáváme jej pomocí $user->getIdentity()
. Můžeme tedy získat id, role a další uživatelská data, tak
jak jsme si je předali v autentikátoru:
Co je důležité, tak že při odhlášení pomocí $user->logout()
se identita nesmaže a je nadále
k dispozici. Takže ačkoliv má uživatel identitu, nemusí být přihlášený. Pokud bychom chtěli identitu explicitně
smazat, odhlásíme uživatele voláním logout(true)
.
Díky tomu můžete nadále předpokládat, který uživatel je u počítače a například mu v e-shopu zobrazovat personalizované nabídky, nicméně zobrazit mu jeho osobní údaje můžete až po přihlášení.
Identita je objekt implementující rozhraní Nette\Security\IIdentity, výchozí implementací je Nette\Security\SimpleIdentity. A jak bylo zmíněno, udržuje se v session, takže pokud tedy například změníme roli některého z přihlášených uživatelů, zůstanou stará data v jeho identitě až do jeho opětovného přihlášení.
Úložiště přihlášeného uživatele
Dvě základní informace o uživateli, tedy zda-li je přihlášen a jeho identita, se zpravidla
přenášejí v session. Což lze změnit. Ukládání těchto informací má na starosti objekt implementující rozhraní
Nette\Security\UserStorage
. K dispozici jsou dvě standardní implementace, první přenáší data v session a
druhá v cookie. Jde o třídy Nette\Bridges\SecurityHttp\SessionStorage
a CookieStorage
. Zvolit si
uložiště a nakonfigurovat jej můžete velmi pohodlně v konfiguraci security ›
authentication.
Dále můžete ovlivnit, jak přesně bude probíhat ukládání identity (sleep) a obnovování (wakeup).
Stačí, aby authenticator implementoval rozhraní Nette\Security\IdentityHandler
. To má dvě metody:
sleepIdentity()
se volá před zápisem identity do úložiště a wakeupIdentity()
po jejím
přečtení. Metody mohou obsah identity upravit, případně ji nahradit novým objektem, který vrátí. Metoda
wakeupIdentity()
může dokonce vrátit null
, čímž uživatele jej odhlásí.
Jako příklad si ukážeme řešení časté otázky, jak aktualizovat role v identitě hned po načtení ze session.
V metodě wakeupIdentity()
předáme do identity aktuální role např. z databáze:
A nyní se vrátíme k úložišti na bázi cookies. Dovoluje vám vytvořit web, kde se mohou přihlašovat uživatelé a
přitom nepotřebuje sessions. Tedy nepotřebuje zapisovat na disk. Ostatně tak funguje i web, který právě čtete, včetně
fóra. V tomto případě je implementace IdentityHandler
nutností. Do cookie totiž budeme ukládat jen náhodný
token reprezentující přihlášeného uživatele.
Nejprve tedy v konfiguraci nastavíme požadované úložiště pomocí
security › authentication › storage: cookie
.
V databázi si vytvoříme sloupec authtoken
, ve kterém bude mít každý uživatel zcela náhodný, unikátní a neuhodnutelný řetězec o dostatečné délce
(alespoň 13 znaků). Úložiště CookieStorage
přenáší v cookie pouze hodnotu
$identity->getId()
, takže v sleepIdentity()
originální identitu nahradíme za zástupnou s
authtoken
v ID, naopak v metodě wakeupIdentity()
podle authtokenu přečteme celou identitu
z databáze:
Více nezávislých přihlášení
Souběžně je možné v rámci jednoho webu a jedné session mít několik nezávislých přihlašujících se uživatelů. Pokud například chceme mít na webu oddělenou autentizaci pro administraci a veřejnou část, stačí každé z nich nastavit vlastní název:
Je důležité pamatovat na to, abychom jmenný prostor nastavili vždy na všech místech patřících do dané části. Pakliže používáme presentery, nastavíme jmenný prostor ve společném předkovi pro danou část – obvykle BasePresenter. Učiníme tak rozšířením metody checkRequirements():
Více autentikátorů
Rozdělení aplikace na části s nezávislým přihlašováním většinou vyžaduje také různé autentikátory. Jakmile
bychom však v konfiguraci služeb zaregistrovali dvě třídy implementující Authenticator, Nette by nevědělo, který
z nich automaticky přiřadit objektu Nette\Security\User
, a zobrazilo by chybu. Proto musíme pro autentikátory autowiring omezit tak, aby fungoval, jen když si někdo vyžádá konkrétní
třídu, např. FrontAuthenticator, čehož docílíme volbou autowired: self
:
Autentikátor objektu User nastavíme před voláním metody login(), takže obvykle v kódu formuláře, který ho přihlašuje: