Novinky v Nette Security 3.1
Budete překvapeni, jaké obzory vám otevře tato nová verze, a jak snadno lze tvořit webové aplikace, které nepotřebují session.
Srozumitelnost
Ještě předtím, než se podíváme na hlavní novinky v úložištích,
musíme si říci, že Nette postupně opouští předponu I
v názvech rozhraní, takže mizí z názvů IAuthenticator
,
IAuthorizator
, IResource
, IRole
,
IUserStorage
. Také třída Identity
byla
přejmenovaná na SimpleIdentity
.
Samozřejmě původní názvy nadále fungují jako aliasy.
Ne vždy jde pouze o přejmenování. Třeba
Nette\Security\UserStorage
je zcela nově navržené rozhraní.
Také Nette\Security\Authenticator
se mírně odlišuje od
IAuthenticator
v tom, že jméno a heslo (a případné další
parametry) se nepředávají v poli, ale jako regulérní parametry:
class Authenticator implements Nette\Security\Authenticator
{
public function authenticate(string $username, string $password): SimpleIdentity
{
...
}
}
Úložiště pro přihlášeného uživatele
Nette udržuje dvě základní informace o uživateli: zda-li je
přihlášen a jeho identitu v podobě objektu implementujícím rozhraní
Nette\Security\IIdentity
. Tyto informace se zpravidla přenášejí
v session. Což můžete v Nette Security 3.1 velmi snadno a přitom
zásadně ovlivnit. Za chvíli se dozvíte, k čemu všemu se to hodí.
Kam se zmíněné informace ukládají určuje tzv. úložiště, což je
objekt implementující rozhraní Nette\Security\UserStorage
.
K dispozici jsou dvě standardní implementace, první přenáší data
v session a druhá, což je novinka, 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.
Nette standardně po přihlášení uživatele identitu serializuje do
session a v dalších požadavcích ji čte. Nyní můžete ovlivnit, co
dalšího se bude při ukládání identity (sleep) a obnovování
(wakeup) dít. Stačí, aby authenticator implementoval rozhraní
Nette\Security\IdentityHandler
a má možnost toto ovlivnit.
Jako příklad si ukážeme řešení časté otázky, jak aktualizovat role v identitě hned po načtení ze session:
final class Authenticator implements Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
public function sleepIdentity(IIdentity $identity): IIdentity
{
// zde lze pozměnit identitu před zápisem do úložiště po přihlášení,
// ale to nyní nepotřebujeme
return $identity;
}
public function wakeupIdentity(IIdentity $identity): ?IIdentity
{
// aktualizace rolí v identitě
$userId = $identity->getId();
$identity->setRoles($this->facade->getUserRoles($userId));
return $identity;
}
V metodě wakeupIdentity()
předáme do identity aktuální
role např. z databáze. Takhle snadné to nyní je.
Samozřejmě kromě role můžeme aktualizovat i další informace,
případně vrátit celou novou identitu. Dokonce lze vrátit null
,
pokud třeba uživatel byl zabanován, čímž jej odhlásíte.
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.
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:
final class Authenticator implements Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
public function authenticate(string $username, string $password): SimpleIdentity
{
$row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username);
// ověříme heslo
...
// vrátíme identitu se všemi údaji z databáze
return new SimpleIdentity($row->id, null, (array) $row);
}
public function sleepIdentity(IIdentity $identity): SimpleIdentity
{
// vrátíme zástupnou identitu, kde v ID bude authtoken
return new SimpleIdentity($identity->authtoken);
}
public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
{
// zástupnou identitu nahradíme plnou identitou, jako v authenticate()
$row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId());
return $row
? new SimpleIdentity($row->id, null, (array) $row)
: null;
}
}
Úložiště přepnete v konfiguraci pomocí
security › authentication › storage: cookie
.
Kompatibilita
Jak bylo řečeno, rozhraní Nette\Security\UserStorage
se zcela
liší od původního IUserStorage
. Implementují jej výše
zmíněné úložiště SessionStorage
a
CookieStorage
. Také metoda
Nette\Security\User::getStorage()
vrací tento nový storage.
Nicméně lze se pomocí konfigurace vrátit k původnímu storage
Nette\Http\UserStorage
, které je deprecated a bude odstraněno ve
verzi 4:
services:
security.userStorage: false
Minimální požadovaná verze je PHP 7.2.
Komentáře
Metody sleepIdentity a wakeupIdentity je opravdu velmi elegantní řešení, moc díky!
Ahoj,
vypadá to super, škoda, že není vyřešený způsob jak přemigrovat uživatele ze session na cookies. A nenašel jsem způsob jak uměle z wakeupIdentity invalidizovat uložený token v cookies, musel jsme v BasePresenteru uživatele uměle odhlásit a přihlásit.
Chcete-li odeslat komentář, přihlaste se