Аутентификация пользователей
****************************
<div class=perex>
Почти ни одно веб-приложение не обходится без механизма входа и выхода пользователей и проверки их прав. В этой главе мы поговорим о том:
- как выполнять вход и выход пользователей
- о собственных аутентификаторах
</div>
→ [Установка и требования |@home#Установка]
В примерах мы будем использовать объект класса [api:Nette\Security\User], представляющий текущего пользователя; получить его можно, попросив передать его через [внедрение зависимостей |dependency-injection:passing-dependencies]. В презентерах достаточно вызвать `$user = $this->getUser()`.
Аутентификация
==============
Аутентификация означает **вход пользователя**, то есть процесс, при котором проверяется личность пользователя. Обычно пользователь удостоверяет свою личность именем и паролем. Проверку выполняет так называемый [аутентификатор |#Аутентификатор]. Если вход не удаётся, выбрасывается `Nette\Security\AuthenticationException`.
```php
try {
$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
$this->flashMessage('Введённое имя пользователя или пароль неверны.');
}
```
Вот так вы выполняете выход пользователя:
```php
$user->logout();
```
А узнать, вошёл ли пользователь, можно так:
```php
echo $user->isLoggedIn() ? 'да' : 'нет';
```
Совсем просто, правда? А обо всех аспектах безопасности за вас заботится Nette.
В презентерах вы можете проверять вход в методе `startup()` и перенаправлять невошедших пользователей на страницу входа.
```php
protected function startup()
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
```
Истечение срока
===============
Срок входа пользователя истекает вместе со сроком [хранилища |#Хранилище вошедшего пользователя], которым обычно является сессия (см. настройку [истечения сессии |http:configuration#Сессия]). Однако вы можете задать и более короткий промежуток времени, после которого пользователь будет разлогинен. Для этого служит метод `setExpiration()`, который вызывается перед `login()`. Передайте аргументом строку с относительным временем:
```php
// вход истекает после 30 минут бездействия
$user->setExpiration('30 minutes');
// отмена заданного срока
$user->setExpiration(null);
```
Метод `$user->getLogoutReason()` подскажет, был ли пользователь разлогинен из-за истечения промежутка времени. Он возвращает либо константу `Nette\Security\User::LogoutInactivity` (истекло ограничение по времени), либо `User::LogoutManual` (был вызван метод `logout()`).
Аутентификатор
==============
Это объект, который проверяет учётные данные для входа, обычно имя пользователя и пароль. Тривиальный его вид - класс [api:Nette\Security\SimpleAuthenticator], который можно определить в [конфигурации|configuration]:
```neon
security:
users:
# имя пользователя: пароль
johndoe: 'secret123'
kathy: 'evenmoresecretpassword'
```
Вместо паролей открытым текстом можно указать и их [хеши |passwords], см. [конфигурацию |configuration]. .{data-version:3.2.6}
Такое решение больше подходит для целей проверки. Мы покажем, как создать аутентификатор, который проверяет учётные данные по таблице базы данных.
Аутентификатор - это объект, реализующий интерфейс [api:Nette\Security\Authenticator] с методом `authenticate()`. Его задача - либо вернуть [личность |#Личность], либо выбросить `Nette\Security\AuthenticationException`. Можно было бы указать и код ошибки, чтобы точнее различить ситуацию: `Authenticator::IdentityNotFound` или `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('Пользователь не найден.');
}
if (!$this->passwords->verify($password, $row->password)) {
throw new Nette\Security\AuthenticationException('Неверный пароль.');
}
return new SimpleIdentity(
$row->id,
$row->role, // либо массив ролей
['name' => $row->username],
);
}
}
```
Класс `MyAuthenticator` общается с базой данных через [Nette Database Explorer |database:explorer] и работает с таблицей `users`, где в столбце `username` находится имя пользователя для входа, а в столбце `password` - [хеш пароля |passwords]. После проверки имени и пароля он возвращает личность, содержащую ID пользователя, его роль (столбец `role` в таблице), о которой мы подробнее поговорим [позже |authorization#Роли], и массив с дополнительными данными (в нашем случае с именем пользователя).
Аутентификатор мы добавим в конфигурацию [как сервис |dependency-injection:services] DI-контейнера:
```neon
services:
- MyAuthenticator
```
События $onLoggedIn, $onLoggedOut
---------------------------------
У объекта `Nette\Security\User` есть [события |nette:glossary#События] `$onLoggedIn` и `$onLoggedOut`, так что вы можете добавить callback'и, которые вызываются соответственно после успешного входа или после выхода пользователя.
```php
$user->onLoggedIn[] = function () {
// пользователь только что вошёл
};
```
Личность
========
Личность (identity) - набор сведений о пользователе, который возвращает аутентификатор и который затем хранится в сессии и доступен через `$user->getIdentity()`. Так мы можем получить ID, роли и другие данные пользователя ровно в том виде, в каком передали их в аутентификаторе:
```php
$user->getIdentity()->getId();
// работает и сокращение $user->getId()
$user->getIdentity()->getRoles();
// данные пользователя доступны как свойства
// имя пользователя, которое мы передали в MyAuthenticator
$user->getIdentity()->name;
```
Важно, что при выходе через `$user->logout()` **личность не удаляется** и остаётся доступной. То есть, даже если у пользователя есть личность, он не обязан быть вошедшим. Если мы хотим удалить личность явно, мы выполняем выход вызовом `logout(true)`.
Благодаря этому вы по-прежнему можете предполагать, какой пользователь сидит за компьютером, и, например, показывать в интернет-магазине персонализированные предложения, но личные сведения показывать только после входа.
.{data-version:3.2.4}
Кроме очистки личности при отдельном вызове `logout(true)`, её сохранение можно полностью отключить свойством `$persistIdentity`. Если оно равно `false`, личность отбрасывается при каждом выходе и при истечении срока, так что `getIdentity()` тогда возвращает `null`. Сохранение личности зависит и от хранилища: хранилище cookie не может сохранить её после выхода, потому что всегда удаляет cookie.
Личность - объект, реализующий интерфейс [api:Nette\Security\IIdentity]. Реализация по умолчанию - [api:Nette\Security\SimpleIdentity]. И, как уже сказано, хранится она в сессии, так что если мы, например, изменим роль одного из вошедших пользователей, старые данные останутся в его личности, пока он не войдёт снова.
Хранилище вошедшего пользователя
================================
Два основных сведения о пользователе, а именно вошёл ли он и его [личность |#Личность], обычно передаются в сессии. Это можно изменить. За хранение этих сведений отвечает объект, реализующий интерфейс `Nette\Security\UserStorage`. Доступны две стандартные реализации: `Nette\Bridges\SecurityHttp\SessionStorage`, передающая данные в сессии, и `CookieStorage`, передающая данные в cookie. Выбрать хранилище и настроить его очень удобно в конфигурации [security › authentication |configuration#Хранилище пользователя].
Кроме того, вы можете повлиять на то, как именно будут проходить сохранение (*sleep*) и восстановление (*wakeup*) личности. Достаточно, чтобы аутентификатор реализовал интерфейс `Nette\Security\IdentityHandler`. Метод `sleepIdentity()` вызывается перед записью личности в хранилище, а `wakeupIdentity()` - после её чтения. Эти методы могут изменить содержимое личности либо заменить её новым объектом, который вернут. Метод `wakeupIdentity()` может даже вернуть `null`, что разлогинит пользователя. Интерфейс объявляет и метод `getGuestIdentity()`, см. [#Личность гостя].
В качестве примера покажем решение частого вопроса о том, как обновить роли в личности сразу после загрузки из сессии. В методе `wakeupIdentity()` мы передаём в личность актуальные роли, например из базы данных:
```php
final class Authenticator implements
Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
public function sleepIdentity(IIdentity $identity): IIdentity
{
// здесь можно изменить личность перед записью в хранилище после входа,
// но сейчас нам это не нужно
return $identity;
}
public function wakeupIdentity(IIdentity $identity): ?IIdentity
{
// обновляем роли в личности
$userId = $identity->getId();
$identity->setRoles($this->facade->getUserRoles($userId));
return $identity;
}
public function getGuestIdentity(): ?IIdentity
{
// личность гостя здесь не используется
return null;
}
```
Теперь вернёмся к хранилищу на основе cookie. Оно позволяет создать сайт, на котором пользователи могут входить, а сессии при этом не нужны. То есть писать на диск не требуется. Именно так работает сайт, который вы сейчас читаете, включая форум. В этом случае реализация `IdentityHandler` необходима. В cookie мы будем хранить только случайный токен, представляющий вошедшего пользователя.
Сначала задайте нужное хранилище в конфигурации через `security › authentication › storage: cookie`.
В базе данных создайте столбец `authtoken`, где у каждого пользователя будет [совершенно случайная, уникальная и неугадываемая |utils:random] строка достаточной длины (не менее 13 символов). `CookieStorage` передаёт в cookie только значение `$identity->getId()`, поэтому в `sleepIdentity()` мы заменяем исходную личность личностью-заместителем, содержащей в ID `authtoken`. И наоборот, в методе `wakeupIdentity()` мы считываем всю личность из базы данных по authtoken:
```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);
// проверяем пароль
// ...
// возвращаем личность со всеми данными из базы данных
return new SimpleIdentity($row->id, null, (array) $row);
}
public function sleepIdentity(IIdentity $identity): SimpleIdentity
{
// возвращаем личность-заместитель, где в ID находится authtoken
return new SimpleIdentity($identity->authtoken);
}
public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
{
// заменяем личность-заместитель полной личностью, как в 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
{
// личность гостя здесь не используется
return null;
}
}
```
Личность гостя .{data-version:3.2.4}
====================================
Иногда удобно, чтобы личность была и у посетителей, которые не вошли, например чтобы дать им набор ролей по умолчанию или какие-то данные. Если аутентификатор реализует `IdentityHandler`, он может предоставить её методом `getGuestIdentity()`, который используется всегда, когда никто не вошёл. Тогда `getIdentity()`, `getId()` и `getRoles()` откатываются к ней, так что у гостей могут быть собственные роли вместо простой роли `guest`. Верните `null`, если личность гостя вам не нужна.
```php
public function getGuestIdentity(): ?IIdentity
{
return new SimpleIdentity('guest', ['guest'], ['name' => 'Guest']);
}
```
Личность гостя никогда не сохраняется в хранилище, и вход всегда её заменяет.
Несколько независимых входов
============================
В рамках одного сайта и одной сессии может одновременно входить несколько независимых пользователей. Например, если мы хотим иметь отдельную аутентификацию для администрирования и для публичной части сайта, достаточно задать каждой из них уникальное пространство имён:
```php
$user->getStorage()->setNamespace('backend');
```
Важно не забыть задавать пространство имён всегда и во всех местах, относящихся к соответствующей части. Если мы используем презентеры, пространство имён мы задаём в общем предке этой части, обычно в BasePresenter. Делаем мы это расширением метода [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]:
```php
public function checkRequirements($element): void
{
$this->getUser()->getStorage()->setNamespace('backend');
parent::checkRequirements($element);
}
```
Если вы переключаете пространство имён в рамках одного запроса (после того, как состояние аутентификации уже прочитано), объект `User` по-прежнему держит состояние, закешированное из предыдущего пространства имён. В таком случае вызовите `refreshStorage()`, чтобы сбросить кеш и заставить перечитать состояние из нового пространства имён:
```php
$user->getStorage()->setNamespace('admin');
$user->refreshStorage(); // перечитываем состояние из нового пространства имён
```
Несколько аутентификаторов
--------------------------
Разделение приложения на части с независимым входом обычно требует и разных аутентификаторов. Однако если бы мы зарегистрировали в конфигурации сервисов два класса, реализующих Authenticator, Nette не знала бы, какой из них автоматически передать объекту `Nette\Security\User`, и показала бы ошибку. Поэтому нам нужно ограничить [autowiring |dependency-injection:autowiring] для аутентификаторов так, чтобы он работал, только когда кто-то запрашивает конкретный класс, например `FrontAuthenticator`. Достигается это выбором `autowired: self`:
```neon
services:
-
create: FrontAuthenticator
autowired: self
```
```php
class SignPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FrontAuthenticator $authenticator,
) {
}
}
```
Аутентификатор объекта User мы задаём перед вызовом метода [login() |api:Nette\Security\User::login()], то есть обычно в коде формы, которая выполняет вход:
```php
$form->onSuccess[] = function (Form $form, \stdClass $data) {
$user = $this->getUser();
$user->setAuthenticator($this->authenticator);
$user->login($data->username, $data->password);
// ...
};
```
Аутентификация пользователей
Почти ни одно веб-приложение не обходится без механизма входа и
выхода пользователей и проверки их прав. В этой главе мы поговорим
о том:
- как выполнять вход и выход пользователей
- о собственных аутентификаторах
→ Установка и требования
В примерах мы будем использовать объект класса Nette\Security\User, представляющий
текущего пользователя; получить его можно, попросив передать его через
внедрение зависимостей. В
презентерах достаточно вызвать $user = $this->getUser().
Аутентификация
Аутентификация означает вход пользователя, то есть процесс, при
котором проверяется личность пользователя. Обычно пользователь
удостоверяет свою личность именем и паролем. Проверку выполняет так
называемый аутентификатор. Если вход не удаётся,
выбрасывается Nette\Security\AuthenticationException.
try {
$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
$this->flashMessage('Введённое имя пользователя или пароль неверны.');
}
Вот так вы выполняете выход пользователя:
$user->logout();
А узнать, вошёл ли пользователь, можно так:
echo $user->isLoggedIn() ? 'да' : 'нет';
Совсем просто, правда? А обо всех аспектах безопасности за вас
заботится Nette.
В презентерах вы можете проверять вход в методе startup() и
перенаправлять невошедших пользователей на страницу входа.
protected function startup()
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
Истечение срока
Срок входа пользователя истекает вместе со сроком хранилища, которым обычно является сессия
(см. настройку истечения сессии).
Однако вы можете задать и более короткий промежуток времени, после
которого пользователь будет разлогинен. Для этого служит метод
setExpiration(), который вызывается перед login(). Передайте
аргументом строку с относительным временем:
// вход истекает после 30 минут бездействия
$user->setExpiration('30 minutes');
// отмена заданного срока
$user->setExpiration(null);
Метод $user->getLogoutReason() подскажет, был ли пользователь
разлогинен из-за истечения промежутка времени. Он возвращает либо
константу Nette\Security\User::LogoutInactivity (истекло ограничение по времени),
либо User::LogoutManual (был вызван метод logout()).
Аутентификатор
Это объект, который проверяет учётные данные для входа, обычно имя
пользователя и пароль. Тривиальный его вид – класс Nette\Security\SimpleAuthenticator,
который можно определить в конфигурации:
security:
users:
# имя пользователя: пароль
johndoe: 'secret123'
kathy: 'evenmoresecretpassword'
Вместо паролей открытым текстом можно указать и их хеши, см. конфигурацию.
Такое решение больше подходит для целей проверки. Мы покажем, как
создать аутентификатор, который проверяет учётные данные по таблице
базы данных.
Аутентификатор – это объект, реализующий интерфейс Nette\Security\Authenticator с методом
authenticate(). Его задача – либо вернуть личность,
либо выбросить Nette\Security\AuthenticationException. Можно было бы указать и код
ошибки, чтобы точнее различить ситуацию: Authenticator::IdentityNotFound или
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('Пользователь не найден.');
}
if (!$this->passwords->verify($password, $row->password)) {
throw new Nette\Security\AuthenticationException('Неверный пароль.');
}
return new SimpleIdentity(
$row->id,
$row->role, // либо массив ролей
['name' => $row->username],
);
}
}
Класс MyAuthenticator общается с базой данных через Nette Database Explorer и работает с таблицей
users, где в столбце username находится имя пользователя для
входа, а в столбце password – хеш
пароля. После проверки имени и пароля он возвращает личность,
содержащую ID пользователя, его роль (столбец role в таблице), о
которой мы подробнее поговорим позже, и массив с дополнительными
данными (в нашем случае с именем пользователя).
Аутентификатор мы добавим в конфигурацию как сервис DI-контейнера:
services:
- MyAuthenticator
События $onLoggedIn, $onLoggedOut
У объекта Nette\Security\User есть события $onLoggedIn и $onLoggedOut,
так что вы можете добавить callback'и, которые вызываются соответственно
после успешного входа или после выхода пользователя.
$user->onLoggedIn[] = function () {
// пользователь только что вошёл
};
Личность
Личность (identity) – набор сведений о пользователе, который возвращает
аутентификатор и который затем хранится в сессии и доступен через
$user->getIdentity(). Так мы можем получить ID, роли и другие данные
пользователя ровно в том виде, в каком передали их в аутентификаторе:
$user->getIdentity()->getId();
// работает и сокращение $user->getId()
$user->getIdentity()->getRoles();
// данные пользователя доступны как свойства
// имя пользователя, которое мы передали в MyAuthenticator
$user->getIdentity()->name;
Важно, что при выходе через $user->logout() личность не
удаляется и остаётся доступной. То есть, даже если у пользователя
есть личность, он не обязан быть вошедшим. Если мы хотим удалить
личность явно, мы выполняем выход вызовом logout(true).
Благодаря этому вы по-прежнему можете предполагать, какой
пользователь сидит за компьютером, и, например, показывать в
интернет-магазине персонализированные предложения, но личные
сведения показывать только после входа.
Кроме очистки личности при отдельном вызове
logout(true), её сохранение можно полностью отключить свойством
$persistIdentity. Если оно равно false, личность отбрасывается при
каждом выходе и при истечении срока, так что getIdentity() тогда
возвращает null. Сохранение личности зависит и от хранилища:
хранилище cookie не может сохранить её после выхода, потому что всегда
удаляет cookie.
Личность – объект, реализующий интерфейс Nette\Security\IIdentity. Реализация по
умолчанию – Nette\Security\SimpleIdentity. И, как
уже сказано, хранится она в сессии, так что если мы, например, изменим
роль одного из вошедших пользователей, старые данные останутся в его
личности, пока он не войдёт снова.
Хранилище вошедшего пользователя
Два основных сведения о пользователе, а именно вошёл ли он и его личность, обычно передаются в сессии. Это можно изменить.
За хранение этих сведений отвечает объект, реализующий интерфейс
Nette\Security\UserStorage. Доступны две стандартные реализации:
Nette\Bridges\SecurityHttp\SessionStorage, передающая данные в сессии, и
CookieStorage, передающая данные в cookie. Выбрать хранилище и настроить
его очень удобно в конфигурации security › authentication.
Кроме того, вы можете повлиять на то, как именно будут проходить
сохранение (sleep) и восстановление (wakeup) личности. Достаточно,
чтобы аутентификатор реализовал интерфейс Nette\Security\IdentityHandler.
Метод sleepIdentity() вызывается перед записью личности в хранилище, а
wakeupIdentity() – после её чтения. Эти методы могут изменить
содержимое личности либо заменить её новым объектом, который вернут.
Метод wakeupIdentity() может даже вернуть null, что разлогинит
пользователя. Интерфейс объявляет и метод getGuestIdentity(), см. Личность гостя.
В качестве примера покажем решение частого вопроса о том, как
обновить роли в личности сразу после загрузки из сессии. В методе
wakeupIdentity() мы передаём в личность актуальные роли, например из
базы данных:
final class Authenticator implements
Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
public function sleepIdentity(IIdentity $identity): IIdentity
{
// здесь можно изменить личность перед записью в хранилище после входа,
// но сейчас нам это не нужно
return $identity;
}
public function wakeupIdentity(IIdentity $identity): ?IIdentity
{
// обновляем роли в личности
$userId = $identity->getId();
$identity->setRoles($this->facade->getUserRoles($userId));
return $identity;
}
public function getGuestIdentity(): ?IIdentity
{
// личность гостя здесь не используется
return null;
}
Теперь вернёмся к хранилищу на основе cookie. Оно позволяет создать
сайт, на котором пользователи могут входить, а сессии при этом не нужны.
То есть писать на диск не требуется. Именно так работает сайт, который
вы сейчас читаете, включая форум. В этом случае реализация
IdentityHandler необходима. В cookie мы будем хранить только случайный
токен, представляющий вошедшего пользователя.
Сначала задайте нужное хранилище в конфигурации через
security › authentication › storage: cookie.
В базе данных создайте столбец authtoken, где у каждого
пользователя будет совершенно случайная,
уникальная и неугадываемая строка достаточной длины (не менее
13 символов). CookieStorage передаёт в cookie только значение
$identity->getId(), поэтому в sleepIdentity() мы заменяем исходную
личность личностью-заместителем, содержащей в ID authtoken. И
наоборот, в методе wakeupIdentity() мы считываем всю личность из базы
данных по authtoken:
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);
// проверяем пароль
// ...
// возвращаем личность со всеми данными из базы данных
return new SimpleIdentity($row->id, null, (array) $row);
}
public function sleepIdentity(IIdentity $identity): SimpleIdentity
{
// возвращаем личность-заместитель, где в ID находится authtoken
return new SimpleIdentity($identity->authtoken);
}
public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
{
// заменяем личность-заместитель полной личностью, как в 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
{
// личность гостя здесь не используется
return null;
}
}
Личность гостя
Иногда удобно, чтобы личность была и у посетителей, которые не вошли,
например чтобы дать им набор ролей по умолчанию или какие-то данные.
Если аутентификатор реализует IdentityHandler, он может предоставить
её методом getGuestIdentity(), который используется всегда, когда никто
не вошёл. Тогда getIdentity(), getId() и getRoles() откатываются
к ней, так что у гостей могут быть собственные роли вместо простой роли
guest. Верните null, если личность гостя вам не нужна.
public function getGuestIdentity(): ?IIdentity
{
return new SimpleIdentity('guest', ['guest'], ['name' => 'Guest']);
}
Личность гостя никогда не сохраняется в хранилище, и вход всегда её
заменяет.
Несколько независимых входов
В рамках одного сайта и одной сессии может одновременно входить
несколько независимых пользователей. Например, если мы хотим иметь
отдельную аутентификацию для администрирования и для публичной части
сайта, достаточно задать каждой из них уникальное
пространство имён:
$user->getStorage()->setNamespace('backend');
Важно не забыть задавать пространство имён всегда и во всех местах,
относящихся к соответствующей части. Если мы используем презентеры,
пространство имён мы задаём в общем предке этой части, обычно в BasePresenter.
Делаем мы это расширением метода checkRequirements():
public function checkRequirements($element): void
{
$this->getUser()->getStorage()->setNamespace('backend');
parent::checkRequirements($element);
}
Если вы переключаете пространство имён в рамках одного запроса
(после того, как состояние аутентификации уже прочитано), объект
User по-прежнему держит состояние, закешированное из предыдущего
пространства имён. В таком случае вызовите refreshStorage(), чтобы
сбросить кеш и заставить перечитать состояние из нового
пространства имён:
$user->getStorage()->setNamespace('admin');
$user->refreshStorage(); // перечитываем состояние из нового пространства имён
Несколько аутентификаторов
Разделение приложения на части с независимым входом обычно требует и
разных аутентификаторов. Однако если бы мы зарегистрировали в
конфигурации сервисов два класса, реализующих Authenticator, Nette не знала бы,
какой из них автоматически передать объекту Nette\Security\User, и
показала бы ошибку. Поэтому нам нужно ограничить autowiring для аутентификаторов так,
чтобы он работал, только когда кто-то запрашивает конкретный класс,
например FrontAuthenticator. Достигается это выбором autowired: self:
services:
-
create: FrontAuthenticator
autowired: self
class SignPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FrontAuthenticator $authenticator,
) {
}
}
Аутентификатор объекта User мы задаём перед вызовом метода login(), то есть обычно в
коде формы, которая выполняет вход:
$form->onSuccess[] = function (Form $form, \stdClass $data) {
$user = $this->getUser();
$user->setAuthenticator($this->authenticator);
$user->login($data->username, $data->password);
// ...
};