Безопасность

Обеспечение безопасности (Security) - это двух шаговый процесс, целью которого является предотвращение доступа пользователя к ресурсам, получить которые он не имеет права.

В первую очередь, система безопасности идентифицирует пользователя, запрашивая у него ту или иную информацию. Этот процесс называется аутентификацией и означает, что система пытается понять, кто вы есть такой.

После того как система идентифицирует вас, следующим шагом требуется определить, имеете ли вы права на доступ к затребованному ресурсу. Эта часть процесса называется авторизацией и подразумевает проверку ваших привилегий на выполнение того или иного действия.

images/book/security_authentication_authorization.png

Поскольку лучший путь изучить что-либо - увидеть на примере, давайте углубимся в вопросы безопасности web-приложений.

Note

security component Symfony доступен как самостоятельная PHP-библиотека и может быть использован в любом PHP-проекте.

Простой пример: базовая HTTP аутентификация

Компонент безопасности может быть настроен при помощи конфигурации приложения. На самом деле, наиболее стандартные сценарии безопасности можно настроить непосредственно через конфигурацию. Следующая конфигурация подскажет Symfony, что нужно защитить любой URL, соответствующий шаблону /admin/*, и запрашивать пользовательские данные при помощи базовой HTTP-аутентификации (т.е. суровый олдскульный бокс username/password):

Tip

Стандартный дистрибутив Symfony выделяет настройку безопасности в отдельный файл (по умолчанию app/config/security.yml). Если вам не нужен отдельный конфигурационный файл для настройки безопасности, вы можете переместить его контент непосредственно в основной конфигурационный файл (по умолчанию app/config/config.yml).

В результате использования такой конфигурации вы получите полнофункциональную систему безопасности, которая выглядит следующим образом:

  • Есть два пользователя системы (ryan and admin);
  • Пользователи аутентифицируются при помощи базовой HTTP-аутентификации;
  • Любой URL, соответствующий шаблону /admin/*, будет защищен, и лишь пользователь admin сможет попасть туда;
  • Любой URL, НЕ соответствующий шаблону /admin/*, будет доступен любому пользователю без ввода логина/пароля.

Давайте взглянем на то, как работает безопасность и как каждая часть конфигурации вступает в игру.

Как работает безопасность: Аутентификация и Авторизация

Система безопасности Symfony работает, определяя “личность” пользователя (аутентификация) и? затем, проверяя, имеет ли этот пользователь доступ к конкретному ресурсу или URL.

Брандмауэры (Аутентификация)

Когда пользователь выполняет запрос к URL, защищённому брандмауэром, активируется система безопасности. Работа брандмауэра заключается в определении того, требуется ли аутентификация пользователя и, если требуется, отправить обратно ответ, инициирующий процесс аутентификации.

Брандмауэр активируется, когда URL входящего запроса соответствует регулярному выражению pattern, которое было указано в конфигурации. В данном примере шаблон pattern (^/) будет соответствовать любому входящему запросу. То, что брандмауэр активируется, не означает, что HTTP аутентификация (бокс с логином/паролем) будет требоваться для каждого URL. К примеру, пользователь может получить доступ к /foo без запроса аутентификации:

images/book/security_anonymous_user_access.png

Это работает, так как брандмауэр позволяет доступ анонимному пользователю на основании параметра anonymous в настройках безопасности. Другими словами, брандмауэр не требует немедленной аутентификации. И, поскольку доступ к /foo не требует никакой особой роли (role) (это указано в секции access_control), запрос будет выполнен без аутентификации пользователя.

Если вы удалите ключ anonymous, брандмауэр будет всегда запрашивать у пользователя немедленной аутентификации.

Контроль доступа (Авторизация)

Если пользователь запрашивает /admin/foo, процесс ведёт себя иным образом. Это обусловлено тем, что в секции access_control указано, что любой URL, соответствующий шаблону ^/admin (т.е. /admin или всё прочее, что соответствует /admin/*) требует наличия у пользователя роли ROLE_ADMIN. Роли являются основой авторизации: пользователь может получить доступ к /admin/foo лишь тогда, когда у него есть роль ROLE_ADMIN.

images/book/security_anonymous_user_denied_authorization.png

Как и ранее, когда пользователь выполняет запрос, брандмауэр не требует идентификации пользователя. Тем не менее, как только контроль доступа отказывает пользователю в действии (так как анонимный пользователь не имеет роли ROLE_ADMIN), брандмауэр вступает в игру и инициирует процесс аутентификации. Процесс аутентификации зависит от механизма аутентификации, который вы используете. Например, если вы используете аутентификацию с использованием формы логина, пользователь будет перенаправлен на страницу логина. Если используется HTTP-аутентификация, пользователю будет направлен ответ с HTTP статус кодом 401 и в браузере будет отображён бокс с полями username и password.

Пользователь теперь имеет возможность отправить свои данные обратно приложению. Если эти данные будут валидными, оригинальный запрос пользователя будет обработан.

images/book/security_ryan_no_role_admin_access.png

В этом примере, пользователь ryan успешно проходит аутентификацию в брандмауэре, но, так как ryan не имеет роли ROLE_ADMIN, он по-прежнему не имеет доступа к /admin/foo. В конечном итоге, это означает, что пользователь увидит некое сообщение, о том, что ему отказано в доступе.

Tip

Когда Symfony запрещает доступ пользователю, ему отображается страница с ошибкой и возвращается HTTP статус код 403 (Forbidden). Вы можете изменить дизайн страницы с ошибкой 403, следуя руководству из книги рецептов Error Pages.

Наконец, если пользователь admin запрашивает /admin/foo, имеет место схожий процесс, за тем исключением, что система контроля доступа разрешит прохождение этого запроса:

images/book/security_admin_role_access.png

Путь запроса, когда пользователь запрашивает защищённый ресурс, прямолинеен, но вместе с тем гибок. Как вы увидите позднее, аутентификация может быть выполнена различными способами, включая форму логина, сертификат X.509 или же посредством Twitter. Вне зависимости от метода аутентификации, запрос проходит следующий путь:

  1. Пользователь запрашивает защищённый ресурс;
  2. Приложение перенаправляет пользователя на форму логина (или её аналог);
  3. Пользователь отправляет свои данные (например username/password);
  4. Брандмауэр производит аутентификацию пользователя;
  5. Аутентифицированный пользователь получает оригинальный запрос.

Note

Процесс аутентификации целиком зависит от типа аутентификации, который вы используете. Например, при использовании формы логина, пользователь отправляет свои данные по URL-адресу, который обрабатывает форму (например, /login_check) и после этого он перенаправляется на изначально запрошенный URL (например, /admin/foo). Но при использовании HTTP аутентификации пользователь отправляет свои данные не уходя с запрошенного URL (например, /admin/foo) и после этого страница отправляется пользователю без перенаправлений.

Эти особенности не должны вам причинять проблем, но лучше о них знать заранее.

Tip

В дальнейшем вы также узнаете, как можно защитить любой объект в Symfony2, включая отдельные контроллеры, объекты и даже PHP-методы.

Используем традиционную форму логина

До сих пор вы узнали, как защитить ваше приложение при помощи брандмауэра и, затем, ограничить доступ к отдельным разделам при помощи ролей. Используя HTTP аутентификацию, вы можете, не прилагая усилий, воспользоваться нативным окошком для аутентификации при помощи логина и пароля. Помимо этого, Symfony поддерживает много механизмов аутентификации “из коробки”. Подробнее с ними вы можете ознакомиться в разделе справочной информации Настройка параметров безопасности.

В этой секции вы улучшите процесс аутентификации, дав пользователю возможность воспользоваться традиционной формой логина.

Во-первых, активируйте форму в брандмауэре:

Tip

Если вы не хотите изменять значения login_path или check_path используемые по умолчанию, вы можете упростить конфигурацию:

Теперь, когда система безопасности инициирует процесс аутентификации, она перенаправляет пользователя на форму логина (/login по умолчанию). Как эта форма будет выглядеть - это ваша забота. Сначала создайте два маршрута: один для отображения формы (т.е. /login) другой будет обрабатывать отправку формы логина (т.е. /login_check):

Note

Вам не требуется реализовывать контроллер для URL /login_check, так как брандмауэр будет автоматически перехватывать и обрабатывать формы, отправленные на этот URL. Не обязательно, но полезно, будет создание маршрута, который вы будете использовать для генерации URL отправки формы в шаблоне логина ниже.

Обратите внимание, что наименование маршрута login не обязательно. Действительно же важным является URL этого маршрута (/login), соответствующий значению параметра login_path, так как на него система безопасности будет перенаправлять пользователя, которому нужно залогиниться.

Затем, создайте контроллер, который будет отображать форму логина:

<?php
// src/Acme/SecurityBundle/Controller/Main;
namespace Acme\SecurityBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;

class SecurityController extends Controller
{
    public function loginAction()
    {
        $request = $this->getRequest();
        $session = $request->getSession();

        // получить ошибки логина, если таковые имеются
        if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
            $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
        } else {
            $error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
        }

        return $this->render('AcmeSecurityBundle:Security:login.html.twig', array(
            // имя, введённое пользователем в последний раз
            'last_username' => $session->get(SecurityContext::LAST_USERNAME),
            'error'         => $error,
        ));
    }
}

Не дайте этому коду запутать вас. Как вы увидите, когда пользователь отправляет форму, система безопасности автоматически обрабатывает её. Если юзер отправил неверные имя и пароль, этот контроллер получает ошибки от системы безопасности, чтобы вы могли их отобразить пользователю.

Другими словами, ваша работа заключается в отображении формы логина и ошибок, которые могут возникнуть, в то время как система безопасности берёт на себя заботу по проверке введённых имени и пароля и аутентификации пользователя.

Наконец, создадим шаблон формы:

Tip

Переменная error, передаваемая в шаблон, это экземпляр класса Symfony\Component\Security\Core\Exception\AuthenticationException. Этот объект может содержать дополнительную информацию - даже секретную - об ошибке аутентификации, так что используйте его с умом!

Форма имеет немного требований. Во-первых, отправляя форму на /login_check (маршрут login_check), система безопасности перехватит отправленную форму и обработает её автоматически. Во вторых, система безопасности ожидает, что отправленные поля будут называться _username и _password (эти наименования также можно настроить).

Вот и всё! Когда вы отправляете форму, система безопасности автоматически проверит данные пользователя и, либо выполнить его аутентификацию, либо отправить пользователя обратно на форму логина, где он увидит возникшие ошибки.

Давайте ещё раз взглянем на процесс целиком:

  1. Пользователь пытается получить доступ к защищённому ресурсу;
  2. Брандмауэр инициирует процесс аутентификации, перенаправляя пользователя на форму логина (/login);
  3. Страница /login отображает форму логина при помощи маршрута и контроллера, созданных в этом примере;
  4. Пользователь отправляет форму логина на URL /login_check;
  5. Система безопасности перехватывает запрос, проверяет данные, отправленные пользователем, аутентифицирует пользователя, если данные верны или же возвращает пользователю страницу логина, если данные не верны.

По умолчанию, если данные пользователя верны, пользователь будет перенаправлен на ту же страницу, которую и запрашивал (например, /admin/foo). Если пользователь сразу открыл страницу логина, то он будет перенаправлен на главную страницу (homepage). Это поведение можно настроить, к примеру разрешить перенаправление пользователя на фиксированный URL.

Дополнительную информацию о том, как настраивается форма логина, смотрите статью в книге рецептов /cookbook/security/form_login.

Авторизация

Первым шагом в обеспечении безопасности всегда является аутентификация: процесс идентификации пользователя. В Symfony аутентификацию можно выполнять различными способами, начиная с базовой HTTP аутентификации и формы логина и заканчивая Facebook.

После того как пользователь аутентифицирован, начинается процесс авторизации. Авторизация является стандартным путём определения имеет ли пользователь право доступа к какому-либо ресурсу (URL, объект модели, вызов метода...). В основе этого процесса лежит присвоение некоторых ролей каждому пользователю и после этого для различных ресурсов можно требовать наличия различных ролей.

Авторизация имеет две различные грани:

  1. Пользователю назначен некоторый набор ролей;
  2. Ресурс требует наличия некоторых ролей для получения доступа к нему.

В этой секции вы узнаете о том, как защитить различные ресурсы (например, URL, вызов метода и т.д.) при помощи различных ролей. Затем, вы узнаете о том, как создаются роли и как их можно присвоить пользователю.

Защищаем URL по шаблону

Наиболее простой и понятный способ защиты вашего приложения - защита некоторого набора URL по шаблону. Вы уже видели ранее, в первом примере этой главы, где все URL, что соответствовали регулярному выражению ^/admin, требовали роли ROLE_ADMIN.

Вы можете определить столько URL, сколько вам нужно - каждый при помощи шаблона для регулярного выражения:

Tip

Добавление в начало пути символа ^ гарантирует, что этому шаблону будут соответствовать лишь URL, которые начинаются c него. Например, путь /admin (без ^ в начале) будет соответствовать как URL /admin/foo, так и URL /foo/admin.

Для каждого входящего запроса, Symfony2 пытается найти соответствующее правило контроля доступа (используется первое найденное правило). Если пользователь ещё не прошёл аутентификацию, инициируется процесс аутентификации (т.е. пользователю предоставляется возможность залогиниться в систему). Если же пользователь уже прошёл аутентификацию, но не имеет требуемой роли, будет брошено исключение Symfony\Component\Security\Core\Exception\AccessDeniedException, которое вы можете обработать и показать пользователю красивую страничку “access denied”. Подробнее читайте в книге рецептов: /cookbook/controller/error_pages

Так как Symfony использует первое найденное правило, URL вида /admin/users/new будет соответствовать первому правилу и требовать наличия роли ROLE_SUPER_ADMIN. Любой URL вида /admin/blog будет соответствовать второму правилу и требовать наличия роли ROLE_ADMIN.

Защита по IP

В жизни могут возникать различные ситуации, в которых вам будет необходимо ограничить доступ для некоего маршрута по IP. Это особенно важно в случае использования Edge Side Includes (ESI), которые, например, используют маршрут под названием “_internal”. Когда используются ESI, маршрут _internal необходим кэширующему шлюзу для подключения различных опций кэширования субсекций внутри указанной страницы. Этот маршрут по умолчанию использует префикс ^/_internal в Symfony Standard Edition (предполагается также что вы раскомментировали эти строки в файле маршрутов).

Ниже приводится пример того, как вы можете защитить этот маршрут от доступа извне:

Использование защищённого канала

Как и защита на основании IP, требование использования SSL добавляется очень просто:

Защита Контроллера

Защищать ваше приложение на основании шаблонов URL легко, но в некоторых случаях может быть не слишком удобным. При необходимости, вы также можете легко форсировать авторизацию внутри контроллера:

<?php
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// ...

public function helloAction($name)
{
    if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
        throw new AccessDeniedException();
    }

    // ...
}

Вы также можете использовать опциональный пакет JMSSecurityExtraBundle, который поможет вам защитить контроллер с использованием аннотаций:

<?php
use JMS\SecurityExtraBundle\Annotation\Secure;

/**
 * @Secure(roles="ROLE_ADMIN")
 */
public function helloAction($name)
{
    // ...
}

Дополнительную информацию об этом пакете вы можете получить из документации JMSSecurityExtraBundle. Если вы используете дистрибутив Symfony Standard Edition, этот пакет уже доступен вам по умолчанию. В противном случае вам необходимо загрузить и установить его.

Защита прочих сервисов

Фактически, всё что угодно в Symfony может быть защищено при помощи стратегии, описанной в предыдущей секции. Например, предположим у вас есть сервис (т.е. PHP класс), который отсылает email-сообщения от одного пользователя другому. Вы можете ограничить использование этого класса - неважно где он будет использован - для пользователей с определённой ролью.

Подробнее о том как вы можете использовать компонент безопасности для защиты различных сервисов и методов в вашем приложении, смотрите статью в книге рецептов: /cookbook/security/securing_services.

Списки контроля доступа (ACL): Защита отдельных объектов базы данных

Представьте, что вы проектируете блог, где пользователи могут создавать комментарии к вашим постам. Теперь вы хотите, чтобы пользователь имел возможность редактировать его собственный комментарий, но не мог редактировать комментарии других пользователей. Также, в качестве администратора, вы хотите иметь возможность редактировать комментарии всех пользователей.

Компонент безопасности содержит опциональную систему “списков контроля доступа” (ACL), которую вы можете использовать при необходимости контроля доступа к отдельным экземплярам объектов в вашей системе. Без использования ACL, вы можете защитить свою систему таким образом, что лишь некоторые пользователи смогут иметь возможность редактирования комментариев. Но с помощью ACL, вы можете ограничить ли разрешить доступ к каждому конкретному комментарию.

Подробнее читайте в книге рецептов: /cookbook/security/acl.

Пользователи

В предыдущей секции вы узнали как можно защитить различные ресурсы, требуя для них наличия одной или нескольких ролей. В этой секции вы узнаете о другой грани авторизации: пользователях.

Откуда берутся пользователи? (Провайдеры Пользователей)

Во время аутентификации, пользователь отправляет некоторые данные (как правило имя и пароль). Работа системы аутентификации заключается в том, чтобы проверить эти данные на некотором наборе пользователей. Откуда же берутся эти пользователи?

В Symfony2 пользователи могут появляться отовсюду - из файла конфигурации, базы данных, веб сервиса или откуда вашей душе угодно будет. Всё, что предоставляет одного или более пользователей системе аутентификации называется “провайдером пользователя” (user provider). Symfony2 поставляется с двумя, наиболее простыми провайдерами: один из них загружает пользователей из конфигурационного файла, другой загружает пользователей из таблицы в базе данных.

Определение пользователей в файле конфигурации

Наиболее простой способ создать пользователей - определить их прямо в файле конфигурации. Фактически вы уже видели этот способ ранее в одном из примеров этой главы.

Такой провайдер называется провайдером “в памяти” (in-memory), так как пользователи не сохранены где-либо в базе данных. В итоге предоставляется объект класса Symfony\Component\Security\Core\User\User.

Tip

Любой провайдер пользователей может загружать пользователей непосредственно из конфигурации, если для него указан параметр users и определены пользователи.

Caution

Если имя вашего пользователя полностью цифровое (например, 77) или содержит тире (например, user-name), вы должны использовать альтернативный синтаксис при создании пользователей в YAML файле:

users:
    - { name: 77, password: pass, roles: 'ROLE_USER' }
    - { name: user-name, password: pass, roles: 'ROLE_USER' }

Для небольших сайтов этот метод быстр и прост в настройке. Для более сложных систем вы вероятно захотите загружать пользователей из базы данных.

Загрузка пользователей из базы данных

Если вы хотите загружать пользователей из базы данных при помощи Doctrine ORM, вы можете этого легко достичь, создав класс User и настроив провайдер entity.

При таком подходе вы сначала создаёте свой собственный класс User, который будет сохраняться в базе данных:

<?php
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class User implements UserInterface
{
    /**
     * @ORM\Column(type="string", length="255")
     */
    protected $username;

    // ...
}

Что же качается системы безопасности, единственным её требованием к вашему классу пользователя является имплементация им интерфейса Symfony\Component\Security\Core\User\UserInterface. Это означает, что ваша концепция пользователя может быть какой угодно, коль скоро класс имплементирует этот интерфейс.

Note

Объект пользователя будет сериализован и сохранён в сессии во время обработки запроса, поэтому рекомендуется также имплементировать интерфейс Serializable для вашего пользователя. Это особенно важно, если ваш класс User имеет родителя с приватными свойствами.

Далее, настроим провайдер entity и укажем для него класс User:

Добавив этот новый провайдер, система аутентификации будет пытаться загрузить объект User из базы данных, используя его поле username.

Note

Этот пример предназначен чтобы показать вам основную идею провайдера entity. Полный рабочий пример приводится в книге рецептов: /cookbook/security/entity_provider.

Больше информации о создании вашего собственного провайдера (например, если вам нужно загружать пользователей из веб-сервиса), смотрите статью /cookbook/security/custom_provider.

Шифрование пароля пользователя

Ранее, для упрощения, все примеры хранили пароли пользователей в виде текста (вне зависимости от того где эти пользователи были определены - в файле настроек или в базе данных). Конечно, в настоящем приложении вы захотите шифровать пароли пользователей из соображений безопасности. Этого легко достичь, связав ваш класс User с одним из нескольких встроенных “процедур шифрования”. Например, при хранении ваших пользователей в памяти, чтобы скрывать их пароли при помощи функции sha1, выполните следующие настройки:

Присвоив параметру iterations значение 1 и параметру encode_as_base64 - false, пароль будет просто прогоняться один раз через алгоритм шифрования sha1 без дополнительного шифрования. Теперь вы можете вычислить хэш пароля програмно (hash('sha1', 'ryanpass')) или же при помощи онлайн-инструментов типа functions-online.com.

Если вы создаёте ваших пользователей динамически (и храните их в базе данных), вы можете использовать более сложные алгоритмы шифрования, а затем передавать оригинал пароля объекту-шифровальщику для хеширования паролей. Например, предположим что ваш объект User - это экземпляр класса Acme\UserBundle\Entity\User (как в примере выше). Сначала настройте шифрование для этого класса пользователя:

В этом случае вы используете более стойкий алгоритм sha512. Также, поскольку вы просто указали алгоритм шифрования в виде строки (sha512), система будет по умолчанию хэшировать ваш пароль 5000 раз подряд и затем шифровать его в base64. Другими словами, пароль будет многократно зашифрован и пароль не сможет быть декодирован (т.е. будет невозможно определить оригинал пароля по его хэшу).

Если у вас предусмотрена некая регистрация для пользователей, вам потребуется определить хэш пароля, чтобы присвоить его пользователю. Вне зависимости от алгоритма шифрования, который вы настроили для объекта пользователя, в контроллере получить хэш пароля можно следующим образом:

<?php
// ...

$factory = $this->get('security.encoder_factory');
$user = new Acme\UserBundle\Entity\User();

$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword('ryanpass', $user->getSalt());
$user->setPassword($password);

Получение объекта пользователя

После аутентификации, объект User для текущего юзера можно получить через сервис security.context. В контроллере это будет выглядеть следующим образом:

<?php
public function indexAction()
{
    $user = $this->get('security.context')->getToken()->getUser();
}

В контроллере можно использвать шорткат:

<?php
public function indexAction()
{
    $user = $this->getUser();
}

Note

Анонимные пользователи технически считаются также аутентифицированными, т.е. метод isAuthenticated() анонимного пользователя будет возвращать true. Для того, чтобы действительно убедиться, что ваш пользователь прошёл аутентификацию, необходимо проверить наличие роли IS_AUTHENTICATED_FULLY.

Использование нескольких провайдеров пользователей

Любой механизм аутентификации (HTTP аутентификация, форма логина и т.п.) использует только один провайдер и будет по умолчанию использовать первый указанный. Но что, если вы хотите указать несколько пользователей при помощи конфигурации и остальных пользователей сохранять в базу данных? Можно создать новый chain-провайдер, который позволит добиться этого:

Теперь, любой механизм аутентификации будет использовать chain_provider, так как он указан первым. В свою очередь, chain_provider будет пытаться получить пользователя как из провайдера in_memory, так и из user_db.

Tip

Если вам не требуется разделять пользователей in_memory от пользователей user_db, вы можете достигнуть того же эффекта ещё быстрее, скомбинировав эти два ресурса в один провайдер:

Вы также можете настроить брандмауэр или индивидуальный механизм аутентификации на использование конкретного провайдера. Напоминаем, если провайдер явно не указан, будет использоваться первый по списку:

В этом примере, если пользователь пытается залогиниться при помощи HTTP аутентификации - будет использоваться провайдер in_memory, но если пользователь попытается залогиниться при помощи формы логина, будет использован провайдер user_db (так как этот провайдер является провайдером по умолчанию для всего брандмауэра).

Подробную информацию о провайдерах пользователей и настройках брандмауэра вы можете прочитать в справочнике: /reference/configuration/security.

Роли

Роль имеет ключевое значение в процессе авторизации. Каждый пользователь получает набор ролей и каждый ресурс требует наличие одной или нескольких ролей. Если пользователь имеет необходимую роль - доступ будет разрешён. В противном случае - доступ будет запрещён.

Роли, по сути, очень просты, это строки, которые вы можете создавать и использовать по мере надобности (тем не менее, внутри системы роли это всё-таки объекты). Например, если вам нужно ограничить доступ к админке блога на вашем сайте, вы можете защитить эту секцию, используя роль ROLE_BLOG_ADMIN. Эта роль не должна быть нигде определена, вы просто начинаете её использовать и всё.

Note

Все роли в Symfony2 должны начинаться с префикса ROLE_. Если вы определяете ваши роли в отдельном классе Role (продвинутый вариант), использовать префикс ROLE_ не нужно.

Иерархические роли

Вместо того, чтобы присваивать пользователю много ролей, вы можете определить правила наследования ролей, создав их иерархию:

В примере выше, пользователь с ролью ROLE_ADMIN будет также иметь роль ROLE_USER. Роль ROLE_SUPER_ADMIN включает в себя ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH, и ROLE_USER (унаследовав её от ROLE_ADMIN).

Выход из системы

Как правило, вы также захотите дать вашим пользователям возможность выйти из системы. К счастью, брандмауэр позволяет обрабатывать выход автоматически, если вы активируете параметр logout в конфигурации:

Будучи настроенной для вашего брандмауэра, эта конфигурация при направлении пользователя на /logout (или любой другой путь, который вы укажете в параметре path) будет де-аутентифицировать его. Этот пользователь будет перенаправлен на главную страницу сайта (также может быть настроено при помощи параметра target). Оба эти параметра - path и target имеют параметры по умолчанию, такие же, как указаны в примере выше. Другими словами, вы можете их не указывать, что упростит настройку:

Отметим также, что вам не нужно создавать контроллер для URL /logout, так как брандмауэр сам позаботится обо всём. Возможно, вы также захотите создать маршрут и использовать его для генерации URL:

После того как пользователь выходит из системы, он будет перенаправлен по пути, указанному в параметре target (например homepage). Подробнее о конфигурации logout читайте в Справочнике по настройке системы безопасности.

Контроль доступа в шаблонах

Если вы хотите проверить, имеет ли пользователь некоторую роль внутри шаблона, воспользуйтесь встроенным хелпером:

Note

Если вы используете эту функцию на странице, URL которой не обрабатывается брандмауэром, будет брошено исключение. Напомним ещё раз, что в большинстве случаев хорошей практикой является наличие главного брандмауэра, который контролирует все URL (как было показано в этой главе).

Контроль доступа в контроллерах

Если вы хотите проверить, имеет ли текущий пользователь ту или иную роль в вашем контроллере, используйте метод isGranted контекста безопасности:

<?php
public function indexAction()
{
    // show different content to admin users
    if ($this->get('security.context')->isGranted('ADMIN')) {
        // Загружаем админ-контент
    }
    // Загружаем прочий контент
}

Note

Брандмауэр должен быть активен, или же будет брошено исключение при вызове метода isGranted. Посмотрите также замечание для шаблонов чуть выше.

Подмена пользователя

Иногда необходимо иметь возможность переключения с одного пользователя на другого без выполнения выхода/входа (например, при отладке или при попытке воспроизвести баг, который пользователь видит, а вы нет). Это можно выполнить при помощи листенера switch_user в брандмауэре:

Для переключения на другого пользователя просто добавьте в строку запроса текущего URL параметр _switch_user и имя пользователя:

Для того, чтобы переключиться обратно, используйте специальное имя _exit:

Естественно, такая возможность должна быть доступна небольшой группе пользователей. По умолчанию, эта функция доступна пользователям с ролью ROLE_ALLOWED_TO_SWITCH. Наименование этой роли можно изменить при помощи опции role. Для большей безопасности вы также можете изменить наименования параметра для строки запроса при помощи опции parameter:

Аутентификация без сохранения состояния (stateless)

По умолчанию, Symfony2 использует куки (сессию) для хранения контекста безопасности пользователя. Но, если вы используете, к примеру, сертификаты или HTTP аутентификацию, сохранение не требуется, так как авторизационные данные доступны для каждого запроса. В этом случае, и если вы не хотите сохранять что-либо между запросами, вы можете активировать stateless аутентификацию (без сохранения состояния, т.е. Symfony2 не будет создавать куки):

Note

Если вы используете форму логина, Symfony2 будет создавать куки всегда, даже если stateless имеет значение true.

Заключение

Безопасность может быть весьма сложным вопросом для решения его в вашем приложении. К счастью, компонент безопасности Symfony следует хорошо зарекомендовавшей себя модели, основанной на аутентификации и авторизации. Аутентификация, которая всегда идёт первой, обрабатывается брандмауэром, чья работа заключается установить “личность” пользователя при помощи любого из доступных методов (HTTP аутентификация, форма логина и т.д.). В книге рецептов вы также найдёте примеры других методов аутентификации, включая то, как реализовать функцию “запомнить меня” при помощи куки.

После того как пользователь аутентифицирован, авторизация может определить имеет ли он доступ к тому или иному ресурсу. Как правило, к URL, классам или методам ставятся в соответствие некоторые роли и если пользователь не имеет требуемой роли, доступ ему будет запрещён. Тем не менее, авторизация это более сложный механизм, следующий системе “голосования”, благодаря которой множество разных участников могут определить имеет ли пользователь права доступа к ресурсу или же нет.

Читайте также в книге рецептов

  • Форсирование HTTP/HTTPS
  • Блэклистинг пользователя по IP при помощи custom voter
  • Списки контроля доступа (ACLs)
  • /cookbook/security/remember_me