Novinky v Nette Security 3.1

před 4 lety od David Grudl  

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

  1. Metody sleepIdentity a wakeupIdentity je opravdu velmi elegantní řešení, moc díky!

    před 4 lety
  2. 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.

    před 4 lety

Chcete-li odeslat komentář, přihlaste se