Контроль доступа (авторизация)
Авторизация определяет, обладает ли пользователь достаточными привилегиями, например, для доступа к определенному ресурсу или выполнения какого-либо действия. Авторизация предполагает успешную аутентификацию, т.е. что пользователь вошел в систему.
В примерах мы будем использовать объект класса Nette\Security\User, который
представляет текущего пользователя и который вы получаете, передавая
его с помощью инъекции зависимостей. В
презентаторах просто вызывайте $user = $this->getUser()
.
Для очень простых сайтов с администрированием, где права
пользователей не различаются, можно использовать в качестве критерия
авторизации уже известный метод isLoggedIn()
. Другими словами: как
только пользователь вошел в систему, он имеет права на все действия и
наоборот.
Роли
Цель ролей – предложить более точное управление правами и
оставаться независимыми от имени пользователя. Как только
пользователь входит в систему, ему назначается одна или несколько
ролей. Сами роли могут быть простыми строками, например, admin
,
member
, guest
и т.д. Они указываются во втором аргументе
конструктора SimpleIdentity
, либо как строка, либо как массив.
В качестве критерия авторизации мы будем использовать метод
isInRole()
, который проверяет, входит ли пользователь в
заданную роль:
Как вы уже знаете, выход пользователя из системы не стирает его
личность. Таким образом, метод getIdentity()
по-прежнему возвращает
объект SimpleIdentity
, включая все предоставленные роли. Nette Framework
придерживается принципа „меньше кода, больше безопасности“, поэтому
при проверке ролей не нужно проверять, вошел ли пользователь в систему.
Метод isInRole()
работает с эффективными ролями, т.е. если
пользователь вошел в систему, то используются роли, назначенные
личности, если он не вошел, то вместо них используется автоматическая
специальная роль guest
.
Авторизатор
В дополнение к ролям мы введем термины ресурс и операция:
- роль – это атрибут пользователя – например, модератор, редактор, посетитель, зарегистрированный пользователь, администратор, …
- ресурс – это логическая единица приложения – статья, страница, пользователь, пункт меню, опрос, ведущий, …
- операция – это конкретное действие, которое пользователь может или не может выполнять с ресурсом – просмотр, редактирование, удаление, голосование, …
Авторизатор – это объект, который решает, имеет ли данная роль
разрешение на выполнение определенной операции с определенным
ресурсом. Это объект, реализующий интерфейс Nette\Security\Authorizator с одним
методом isAllowed()
:
Мы добавляем авторизатор в конфигурацию как сервис контейнера DI:
И ниже приведен пример использования. Обратите внимание, что в этот
раз мы вызываем метод Nette\Security\User::isAllowed()
, а не метод
авторизатора, поэтому нет первого параметра $role
. Этот метод
вызывает MyAuthorizator::isAllowed()
последовательно для всех ролей
пользователей и возвращает true, если хотя бы один из них имеет
разрешение.
Оба аргумента являются необязательными, и их значение по умолчанию означает все.
Разрешение ACL
Nette поставляется со встроенной реализацией авторизатора, классом Nette\Security\Permission, который предлагает легкий и гибкий уровень ACL (Access Control List) для разрешения и контроля доступа. Когда мы работаем с этим классом, мы определяем роли, ресурсы и отдельные разрешения. При этом роли и ресурсы могут образовывать иерархии. Чтобы объяснить это, мы покажем пример веб-приложения:
guest
: посетитель, не вошедший в систему, которому разрешено читать и просматривать публичную часть сайта, т.е. читать статьи, комментировать и голосовать в опросах.registered
: вошедший в систему пользователь, который, помимо этого, может оставлять комментарии.admin
: может управлять статьями, комментариями и опросами
Итак, мы определили определенные роли (guest
, registered
и
admin
) и упомянули ресурсы (article
, comments
, poll
), к
которым пользователи могут получить доступ или предпринять действия
(view
, vote
, add
, edit
).
Мы создаем экземпляр класса Permission и определяем роли. Можно
использовать наследование ролей, что гарантирует, что, например,
пользователь с ролью admin
может делать то, что может делать
обычный посетитель сайта (и, конечно, больше).
Теперь мы определим список ресурсов, к которым пользователи могут получить доступ:
Ресурсы также могут использовать наследование, например, мы можем
добавить $acl->addResource('perex', 'article')
.
А теперь самое главное. Мы определим между ними правила, определяющие, кто что может делать:
Что если мы хотим препятствовать кому-то получить доступ к ресурсу?
Теперь, когда мы создали набор правил, мы можем просто задавать запросы на авторизацию:
То же самое относится и к зарегистрированному пользователю, но он также может комментировать:
Администратор может редактировать все, кроме опросов:
Разрешения также могут оцениваться динамически, и мы можем оставить решение за нашим собственным обратным вызовом, которому передаются все параметры:
Но как решить ситуацию, когда имен ролей и ресурсов недостаточно, т.е.
мы хотим определить, что, например, роль registered
может
редактировать ресурс article
только если она является его автором?
Мы будем использовать объекты вместо строк, роль будет объектом Nette\Security\Role и источником Nette\Security\Resource. Их методы
getRoleId()
и getResourceId()
будут возвращать исходные строки:
А теперь давайте создадим правило:
ACL запрашивается путем передачи объектов:
Роль может наследоваться от одной или нескольких других ролей. Но что произойдет, если у одного предка определенное действие разрешено, а у другого – запрещено? Тогда в игру вступает вес роли – последняя роль в массиве наследуемых ролей имеет наибольший вес, первая – наименьший:
Роли и ресурсы также могут быть удалены (removeRole()
,
removeResource()
), правила также могут быть отменены (removeAllow()
,
removeDeny()
). Массив всех ролей прямых родителей возвращает
getRoleParents()
. Наследуются ли две сущности друг от друга,
возвращается roleInheritsFrom()
и resourceInheritsFrom()
.
Добавить как службу
Нам нужно добавить созданный нами ACL в конфигурацию как сервис, чтобы
его мог использовать объект $user
, т.е. чтобы мы могли использовать
в коде, например, $user->isAllowed('article', 'view')
. Для этого мы напишем для
него фабрику:
И добавим ее в конфигурацию:
В ведущих вы можете затем проверить разрешения в методе startup()
,
например: