Аутентификация пользователей
Мало-мальски значимые веб-приложения не нуждаются в механизме для входа пользователей в систему или проверки их привилегий. В этой главе мы поговорим о:
- вход и выход пользователя
- пользовательские аутентификаторы и авторизаторы
В примерах мы будем использовать объект класса Nette\Security\User, который
представляет текущего пользователя и который вы получаете, передавая
его с помощью инъекции зависимостей. В
презентаторах просто вызывайте $user = $this->getUser()
.
Аутентификация
Аутентификация означает вход пользователя в систему, т.е.
процесс, в ходе которого проверяется личность пользователя.
Пользователь обычно идентифицирует себя с помощью имени пользователя
и пароля. Верификация выполняется так называемым аутентификатором. Если вход в систему не удается,
происходит выброс Nette\Security\AuthenticationException
.
Вот как выйти из системы:
И проверить, вошел ли пользователь в систему:
Просто, правда? И все аспекты безопасности обрабатываются Nette за вас.
В Presenter вы можете проверить вход в систему в методе startup()
и
перенаправить незалогиненного пользователя на страницу входа.
Срок действия
Логин пользователя истекает вместе с истечением срока действия репозитория, который
обычно является сессией (см. настройку истечения
срока действия сессии ). Однако можно задать и более короткий
промежуток времени, по истечении которого пользователь выходит из
системы. Для этого используется метод setExpiration()
, который
вызывается перед login()
. В качестве параметра предоставьте строку
с относительным временем:
Метод $user->getLogoutReason()
определяет, вышел ли пользователь из
системы, поскольку истек временной интервал. Он возвращает либо
константу Nette\Security\UserStorage::LogoutInactivity
, если время истекло, либо
UserStorage::LogoutManual
, если был вызван метод logout()
.
Аутентификатор
Это объект, который проверяет данные для входа в систему, т.е. обычно имя и пароль. Тривиальной реализацией является класс Nette\Security\SimpleAuthenticator, который может быть определен в конфигурации:
Это решение больше подходит для целей тестирования. Мы покажем вам, как создать аутентификатор, который будет проверять учетные данные по таблице базы данных.
Аутентификатор – это объект, реализующий интерфейс Nette\Security\Authenticator с методом
authenticate()
. Его задача – либо вернуть так называемый идентификатор, либо выбросить исключение
Nette\Security\AuthenticationException
. Также можно было бы предоставить код
ошибки Authenticator::IdentityNotFound
или Authenticator::InvalidCredential
.
Класс MyAuthenticator взаимодействует с базой данных через Nette Database Explorer и работает с таблицей
users
, где столбец username
содержит имя пользователя для входа
в систему, а столбец password
– хэш. После проверки
имени и пароля он возвращает идентификатор с ID пользователя, роль
(столбец role
в таблице), которую мы упомянем позже, и
массив с дополнительными данными (в нашем случае имя пользователя).
Мы добавим аутентификатор в конфигурацию как сервис контейнера DI:
$onLoggedIn, $onLoggedOut Events
Объект Nette\Security\User
имеет события $onLoggedIn
и $onLoggedOut
,
поэтому вы можете добавить обратные вызовы, которые срабатывают после
успешного входа в систему или после выхода пользователя из системы.
Идентичность
Идентификатор – это набор информации о пользователе, который
возвращается аутентификатором и который затем хранится в сессии и
извлекается с помощью $user->getIdentity()
. Таким образом, мы можем
получить id, роли и другие данные пользователя в том виде, в котором мы
передали их в аутентификаторе:
Важно отметить, что когда пользователь выходит из системы с помощью
$user->logout()
, идентичность не удаляется и все еще доступна.
Таким образом, если идентификатор существует, он сам по себе не
гарантирует, что пользователь также вошел в систему. Если мы хотим
явным образом удалить идентификатор, мы выходим из системы с помощью
logout(true)
.
Благодаря этому вы все еще можете определить, какой пользователь находится за компьютером, и, например, отображать персонализированные предложения в интернет-магазине, однако вы можете отображать его личные данные только после входа в систему.
Identity – это объект, реализующий интерфейс Nette\Security\IIdentity, реализация по умолчанию – Nette\Security\SimpleIdentity. Как уже упоминалось, идентификатор хранится в сессии, поэтому если, например, мы изменим роль какого-то из вошедших в систему пользователей, старые данные будут храниться в идентификаторе до тех пор, пока он снова не войдет в систему.
Хранение данных для вошедшего пользователя
Две основные части информации о пользователе, т.е. вошел ли он в
систему и его личность, обычно хранятся в сессии.
Которая может быть изменена. За хранение этой информации отвечает
объект, реализующий интерфейс Nette\Security\UserStorage
. Существует две
стандартные реализации, первая передает данные в сессии, вторая – в
cookie. Это классы Nette\Bridges\SecurityHttp\SessionStorage
и CookieStorage
. Выбрать
хранилище и настроить его очень удобно в конфигурации security › authentication.
Вы также можете контролировать, как именно будет происходить
сохранение (sleep) и восстановление (wakeup) аутентификации. Все, что
вам нужно, это чтобы аутентификатор реализовывал интерфейс
Nette\Security\IdentityHandler
. У него есть два метода: sleepIdentity()
вызывается перед записью идентификатора в хранилище, а
wakeupIdentity()
– после считывания идентификатора. Эти методы могут
изменять содержимое идентификатора или заменять его новым объектом,
который возвращается. Метод wakeupIdentity()
может даже возвращать
null
, который выводит пользователя из системы.
В качестве примера мы покажем решение распространенного вопроса о
том, как обновить роли идентификатора сразу после восстановления из
сессии. В методе wakeupIdentity()
мы передаем идентификатору текущие
роли, например, из базы данных:
А теперь вернемся к хранилищу на основе cookie. Оно позволяет создать
сайт, на котором пользователи могут входить в систему без
необходимости использования сессий. Поэтому ему не требуется запись
на диск. В конце концов, именно так работает сайт, который вы сейчас
читаете, включая форум. В этом случае реализация IdentityHandler
является необходимостью. Мы будем хранить в cookie только случайный
токен, представляющий вошедшего пользователя.
Поэтому сначала мы зададим нужное хранилище в конфигурации с помощью
security › authentication › storage: cookie
.
Мы добавим в базу данных колонку authtoken
, в которой каждый
пользователь будет иметь совершенно
случайную, уникальную и не угадываемую строку достаточной длины (не
менее 13 символов). Хранилище CookieStorage
хранит только значение
$identity->getId()
в cookie, поэтому в методе sleepIdentity()
мы заменим
оригинальную личность на прокси с authtoken
в ID, а в методе
wakeupIdentity()
, наоборот, восстановим всю личность из базы данных по
auttoken:
Множественная независимая аутентификация
Можно иметь несколько независимых зарегистрированных пользователей в рамках одного сайта и одной сессии одновременно. Например, если мы хотим иметь отдельную аутентификацию для frontend и backend, мы просто установим уникальное пространство имен сессии для каждого из них:
Необходимо помнить, что оно должно быть задано во всех местах, принадлежащих одному сегменту. При использовании презентаторов мы установим пространство имен в общем предке – обычно BasePresenter. Для этого мы расширим метод checkRequirements():
Множественные аутентификаторы
Разделение приложения на сегменты с независимой аутентификацией
обычно требует использования разных аутентификаторов. Однако
регистрация двух классов, реализующих Authenticator, в конфигурационных
службах приведет к ошибке, поскольку Nette не будет знать, какой из них
должен быть автоподключен к объекту
Nette\Security\User
. Вот почему мы должны ограничить автоподключение для
них с помощью autowired: self
так, чтобы оно активировалось только при
конкретном запросе их класса:
Нам нужно установить наш аутентификатор на объект User только перед вызовом метода login(), что обычно означает в обратном вызове формы входа: