Přihlašování uživatelů (Autentizace)
************************************
<div class=perex>
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
</div>
→ [Instalace a požadavky |@home#Instalace]
V příkladech budeme používat objekt třídy [api: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 |dependency-injection:passing-dependencies]. 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`.
```php
try {
$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
$this->flashMessage('Uživatelské jméno nebo heslo je nesprávné');
}
```
Tímto způsobem uživatele odhlásíte:
```php
$user->logout();
```
A zjištění, že je přihlášen:
```php
echo $user->isLoggedIn() ? 'ano' : 'ne';
```
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.
```php
protected function startup()
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
```
Expirace
========
Přihlášení uživatele expiruje společně s [expirací úložiště |#Úložiště přihlášeného uživatele], kterým je obvykle session (viz nastavení [expirace session |http:configuration#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:
```php
// přihlášení vyprší po 30 minutách neaktivity
$user->setExpiration('30 minutes');
// zrušení nastavené expirace
$user->setExpiration(null);
```
Jestli byl uživatel odhlášen z důvodu vypršení časového intervalu, prozradí metoda `$user->getLogoutReason()`, která vrací buď konstantu `Nette\Security\User::LogoutInactivity` (vypršel časový limit), nebo `User::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 [api:Nette\Security\SimpleAuthenticator], kterou můžeme nadefinovat v [konfiguraci|configuration]:
```neon
security:
users:
# jméno: heslo
frantisek: 'tajneheslo'
katka: 'jestetajnejsiheslo'
```
Místo hesel v čitelné podobě lze uvádět i jejich [hashe |passwords], viz [konfigurace |configuration]. .{data-version:3.2.6}
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í [api:Nette\Security\Authenticator] s metodou `authenticate()`. Jejím úkolem je buď vrátit tzv. [identitu |#Identita], 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`.
```php
use Nette;
use Nette\Security\SimpleIdentity;
class MyAuthenticator implements Nette\Security\Authenticator
{
public function __construct(
private Nette\Database\Explorer $database,
private Nette\Security\Passwords $passwords,
) {
}
public function authenticate(string $username, string $password): SimpleIdentity
{
$row = $this->database->table('users')
->where('username', $username)
->fetch();
if (!$row) {
throw new Nette\Security\AuthenticationException('User not found.');
}
if (!$this->passwords->verify($password, $row->password)) {
throw new Nette\Security\AuthenticationException('Invalid password.');
}
return new SimpleIdentity(
$row->id,
$row->role, // nebo pole více rolí
['name' => $row->username],
);
}
}
```
Třída MyAuthenticator komunikuje s databází prostřednictvím [Nette Database Explorer|database:explorer] a pracuje s tabulkou `users`, kde je ve sloupci `username` přihlašovací jméno uživatele a ve sloupci `password` [otisk hesla|passwords]. Po ověření jména a hesla vrací identitu, která nese ID uživatele, jeho [roli |authorization#Role] (sloupec `role` v tabulce) 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|dependency-injection:services] DI kontejneru:
```neon
services:
- MyAuthenticator
```
Události $onLoggedIn, $onLoggedOut
----------------------------------
Objekt `Nette\Security\User` má [události |nette:glossary#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.
```php
$user->onLoggedIn[] = function () {
// uživatel byl právě přihlášen
};
```
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:
```php
$user->getIdentity()->getId();
// funguje i zkratka $user->getId();
$user->getIdentity()->getRoles();
// uživatelská data jsou dostupná jako properties
// jméno, které jsme si předali v MyAuthenticator
$user->getIdentity()->name;
```
Důležité je, ž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í.
.{data-version:3.2.4}
Kromě smazání identity u konkrétního volání pomocí `logout(true)` lze její uchovávání vypnout úplně vlastností `$persistIdentity`. Když ji nastavíte na `false`, identita se zahodí při každém odhlášení i při expiraci, takže `getIdentity()` pak vrací `null`. Uchování identity navíc závisí na úložišti: cookie úložiště ji po odhlášení neuchová, protože cookie vždy smaže.
Identita je objekt implementující rozhraní [api:Nette\Security\IIdentity], výchozí implementací je [api: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 zdali 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 úložiště a nakonfigurovat jej můžete velmi pohodlně v konfiguraci [security › authentication |configuration#Úložiště].
Dále můžete ovlivnit, jak přesně bude probíhat ukládání identity (*sleep*) a obnovování (*wakeup*). Stačí, aby autentikátor implementoval rozhraní `Nette\Security\IdentityHandler`. Metoda `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 odhlásí. Rozhraní deklaruje i metodu `getGuestIdentity()`, viz [#Identita hosta].
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:
```php
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;
}
public function getGuestIdentity(): ?IIdentity
{
// identitu hosta zde nepoužíváme
return null;
}
```
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ý|utils:random] ř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:
```php
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;
}
public function getGuestIdentity(): ?IIdentity
{
// identitu hosta zde nepoužíváme
return null;
}
}
```
Identita hosta .{data-version:3.2.4}
====================================
Někdy se hodí, aby měli identitu i nepřihlášení návštěvníci, například abyste jim přiřadili výchozí sadu rolí nebo jim předali nějaká data. Pokud autentikátor implementuje `IdentityHandler`, může ji poskytnout přes metodu `getGuestIdentity()`, která se použije vždy, když není nikdo přihlášený. Metody `getIdentity()`, `getId()` a `getRoles()` se pak na ni odkážou, takže hosté mohou mít vlastní role místo pouhé role `guest`. Pokud identitu hosta nechcete, vraťte `null`.
```php
public function getGuestIdentity(): ?IIdentity
{
return new SimpleIdentity('guest', ['guest'], ['name' => 'Guest']);
}
```
Identita hosta se nikdy neukládá do úložiště a přihlášení ji vždy nahradí.
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:
```php
$user->getStorage()->setNamespace('backend');
```
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() |api:Nette\Application\UI\Presenter::checkRequirements()]:
```php
public function checkRequirements($element): void
{
$this->getUser()->getStorage()->setNamespace('backend');
parent::checkRequirements($element);
}
```
Pokud jmenný prostor přepnete během jednoho požadavku (poté, co už byl přihlašovací stav načten), objekt `User` stále drží stav cachovaný z předchozího jmenného prostoru. V takovém případě zavolejte `refreshStorage()`, který cache zahodí a vynutí opětovné načtení z nového jmenného prostoru:
```php
$user->getStorage()->setNamespace('admin');
$user->refreshStorage(); // znovu načte stav z nového jmenného prostoru
```
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 |dependency-injection: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`:
```neon
services:
-
create: FrontAuthenticator
autowired: self
```
```php
class SignPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FrontAuthenticator $authenticator,
) {
}
}
```
Autentikátor objektu User nastavíme před voláním metody [login() |api:Nette\Security\User::login()], takže obvykle v kódu formuláře, který ho přihlašuje:
```php
$form->onSuccess[] = function (Form $form, \stdClass $data) {
$user = $this->getUser();
$user->setAuthenticator($this->authenticator);
$user->login($data->username, $data->password);
// ...
};
```
{{maintitle: Přihlašování uživatelů v PHP s Nette: autentizace a identita}}
{{description: Přihlašování a odhlašování uživatelů v Nette: vlastní autentikátor, práce s identitou, expirace přihlášení a více nezávislých přihlášení vedle sebe.}}
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
→ Instalace a požadavky
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.
try {
$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
$this->flashMessage('Uživatelské jméno nebo heslo je nesprávné');
}
Tímto způsobem uživatele odhlásíte:
$user->logout();
A zjištění, že je přihlášen:
echo $user->isLoggedIn() ? 'ano' : 'ne';
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.
protected function startup()
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
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:
// přihlášení vyprší po 30 minutách neaktivity
$user->setExpiration('30 minutes');
// zrušení nastavené expirace
$user->setExpiration(null);
Jestli byl uživatel odhlášen z důvodu vypršení časového intervalu, prozradí metoda
$user->getLogoutReason(), která vrací buď konstantu Nette\Security\User::LogoutInactivity
(vypršel časový limit), nebo User::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:
security:
users:
# jméno: heslo
frantisek: 'tajneheslo'
katka: 'jestetajnejsiheslo'
Místo hesel v čitelné podobě lze uvádět i jejich hashe, viz konfigurace.
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.
use Nette;
use Nette\Security\SimpleIdentity;
class MyAuthenticator implements Nette\Security\Authenticator
{
public function __construct(
private Nette\Database\Explorer $database,
private Nette\Security\Passwords $passwords,
) {
}
public function authenticate(string $username, string $password): SimpleIdentity
{
$row = $this->database->table('users')
->where('username', $username)
->fetch();
if (!$row) {
throw new Nette\Security\AuthenticationException('User not found.');
}
if (!$this->passwords->verify($password, $row->password)) {
throw new Nette\Security\AuthenticationException('Invalid password.');
}
return new SimpleIdentity(
$row->id,
$row->role, // nebo pole více rolí
['name' => $row->username],
);
}
}
Třída MyAuthenticator komunikuje s databází prostřednictvím Nette
Database Explorer a pracuje s tabulkou users, kde je ve 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) 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:
services:
- MyAuthenticator
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.
$user->onLoggedIn[] = function () {
// uživatel byl právě přihlášen
};
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:
$user->getIdentity()->getId();
// funguje i zkratka $user->getId();
$user->getIdentity()->getRoles();
// uživatelská data jsou dostupná jako properties
// jméno, které jsme si předali v MyAuthenticator
$user->getIdentity()->name;
Důležité je, ž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í.
Kromě smazání identity u konkrétního volání pomocí logout(true) lze její
uchovávání vypnout úplně vlastností $persistIdentity. Když ji nastavíte na false, identita se
zahodí při každém odhlášení i při expiraci, takže getIdentity() pak vrací null. Uchování
identity navíc závisí na úložišti: cookie úložiště ji po odhlášení neuchová, protože cookie vždy smaže.
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 zdali 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
úlož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 autentikátor implementoval rozhraní Nette\Security\IdentityHandler. Metoda
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 odhlásí. Rozhraní deklaruje
i metodu getGuestIdentity(), viz Identita hosta.
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:
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;
}
public function getGuestIdentity(): ?IIdentity
{
// identitu hosta zde nepoužíváme
return null;
}
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:
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;
}
public function getGuestIdentity(): ?IIdentity
{
// identitu hosta zde nepoužíváme
return null;
}
}
Identita hosta
Někdy se hodí, aby měli identitu i nepřihlášení návštěvníci, například abyste jim přiřadili výchozí sadu
rolí nebo jim předali nějaká data. Pokud autentikátor implementuje IdentityHandler, může ji poskytnout přes
metodu getGuestIdentity(), která se použije vždy, když není nikdo přihlášený. Metody
getIdentity(), getId() a getRoles() se pak na ni odkážou, takže hosté mohou mít
vlastní role místo pouhé role guest. Pokud identitu hosta nechcete, vraťte null.
public function getGuestIdentity(): ?IIdentity
{
return new SimpleIdentity('guest', ['guest'], ['name' => 'Guest']);
}
Identita hosta se nikdy neukládá do úložiště a přihlášení ji vždy nahradí.
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:
$user->getStorage()->setNamespace('backend');
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():
public function checkRequirements($element): void
{
$this->getUser()->getStorage()->setNamespace('backend');
parent::checkRequirements($element);
}
Pokud jmenný prostor přepnete během jednoho požadavku (poté, co už byl přihlašovací stav načten), objekt
User stále drží stav cachovaný z předchozího jmenného prostoru. V takovém případě zavolejte
refreshStorage(), který cache zahodí a vynutí opětovné načtení z nového jmenného prostoru:
$user->getStorage()->setNamespace('admin');
$user->refreshStorage(); // znovu načte stav z nového jmenného prostoru
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:
services:
-
create: FrontAuthenticator
autowired: self
class SignPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FrontAuthenticator $authenticator,
) {
}
}
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:
$form->onSuccess[] = function (Form $form, \stdClass $data) {
$user = $this->getUser();
$user->setAuthenticator($this->authenticator);
$user->login($data->username, $data->password);
// ...
};