Обзор компонентов Symfony2 : Авторизация

В случае если любой из провайдеров аутентификации (см. Провайдеры аутентификации) подтвердит непроверенный токен, то будет возвращен аутентифицированный токен. Аутентификационный подписчик должен напрямую установить этот токен в TokenStorageInterface при помощи метода [setToken()](http://api.symfony.com/2.6/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorageInterface.html#setToken()).

После этого пользователь считается аутентифицированным, то есть идентифицированным. Теперь в любой части приложения вы можете использовать этот токен для определения доступен ли определенный URL пользователю или может ли он изменять определенный объект. Само решение о праве доступа принимается экземпляром AccessDecisionManagerInterface.

Решение об авторизации всегда принимается основываясь на нескольких моментах:

  • Текущий токен — Например, метод [getRoles()](http://api.symfony.com/2.6/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.html#getRoles()) токена можно использовать для определения ролей пользователя (ROLE_SUPER_ADMIN), а сам класс токена можно использовать для принятия решения о аутентификации.
  • Набор атрибутов — каждый атрибут отвечает за определенное право, которым обладает пользователь, например, ROLE_ADMIN говорит о том, что пользователь является администратором.
  • Объект (опционально) — любой объект, доступ к которому определяется, например, объект статьи или комментария.

TokenStorageInterface впервые был представлен в Symfony 2.6. В более ранних версиях приходилось использовать метод setToken() из SecurityContextInterface.

Менеджер доступа

Так как процесс принятия решения о наличии или отсутствии прав доступа для пользователя на операцию, может быть довольно сложным, стандартный AccessDecisionManager зависит от нескольких voter’ов, а окончательное решение принимается основываясь на всех voter’ах.

Существует несколько стратегий:

  • affirmative (утвердительная, по-умолчанию) — доступ открывается сразу, как только один из voter’ов вернул положительный ответ
  • consensus (согласование) — досутп открывается, если voter’ов с положительным ответом больше, чем с отрицательным
  • (unanimous) единогласие — доступ открывается только в том случае, если все voter’ы дали утверждение
<?php
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
// instances of Symfony\Component\Security\Core\Authorization\Voter\VoterInterface
$voters = array(...);
// one of "affirmative", "consensus", "unanimous"
$strategy = ...;
// whether or not to grant access when all voters abstain
$allowIfAllAbstainDecisions = ...;
// whether or not to grant access when there is no majority (applies only to the "consensus" strategy)
$allowIfEqualGrantedDeniedDecisions = ...;
$accessDecisionManager = new AccessDecisionManager(
   $voters,
   $strategy,
   $allowIfAllAbstainDecisions,
   $allowIfEqualGrantedDeniedDecisions
);

Стратегию принятия решения можно изменить в конфигурации.

Voter’ы

Все voter’ы является экземплярами VoterInterface, это означает, что каждый класс voter’а должен реализовать несколько методов для принятия решения:

  • supportsAttribute($attribute) — используется для определения способен ли voter обработать атрибут
  • supportsClass($class) — используется для определения может ли voter открыть или закрыть доступ к определенному классу
  • vote(TokenInterface $token, $object, array $attributes) — именно этот метод принимает решение об открытии или закрытии доступа и возвращает одну из следующих констант: VoterInterface::ACCESS_GRANTED, VoterInterface::ACCESS_DENIED или VoterInterface::ACCESS_ABSTAIN

Компонент Security содержит в себе несколько стандартных voter’ов, которых должно быть достаточно в большинстве случаев:

AuthenticatedVoter

AuthenticatedVoter поддерживает атрибуты IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, и IS_AUTHENTICATED_ANONYMOUSLY и открывает доступ в зависимости от текущего уровня аутентификации, то есть определяет аутентифицирован ли пользователь полностью или только при помощи cookie, или вообще аутентифицирован как аноним.

<?php
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
$anonymousClass = 'Symfony\Component\Security\Core\Authentication\Token\AnonymousToken';
$rememberMeClass = 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken';
$trustResolver = new AuthenticationTrustResolver($anonymousClass, $rememberMeClass);
$authenticatedVoter = new AuthenticatedVoter($trustResolver);
// instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface
$token = ...;
// any object
$object = ...;
$vote = $authenticatedVoter->vote($token, $object, array('IS_AUTHENTICATED_FULLY');

RoleVoter

RoleVoter поддерживает атрибуты начинающиеся с ROLE_ и открывает доступ в том случае, если роль присутствует в массиве ролей пользователя, полученном при помощи метода getRoles():

<?php
use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
$roleVoter = new RoleVoter('ROLE_');
$roleVoter->vote($token, $object, array('ROLE_ADMIN'));

RoleHierarchyVoter

RoleHierarchyVoter наследует RoleVoter и добавляет некоторый функционал к нему: он умеет обрабатывать иерархию ролей. Например, роль ROLE_SUPER_ADMIN может содержать подроли ROLE_ADMIN и ROLE_USER, таким образом если некоторый объект требует наличия у пользователя роли ROLE_ADMIN, то доступ будет открыт не только тому, кто имеет эту роль, но также и пользователю с ролью ROLE_SUPER_ADMIN:

<?php
use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
$hierarchy = array(
   'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_USER'),
);
$roleHierarchy = new RoleHierarchy($hierarchy);
$roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy);

При создании своего voter’а вы, конечно, можете добавить в конструкторе необходимые вам зависимости.

Роли

Роли — объекты, отображающие определенный набор прав пользователя. Существует только одно правило — класс должен реализовать RoleInterface, то есть необходимо прописать методы [getRole()](http://api.symfony.com/2.6/Symfony/Component/Security/Core/Role/Role/RoleInterface.html#getRole()), который должен вернуть роль в строковом виде. Стандартный класс Role просто возвращает первый аргумент конструктора.

<?php
use Symfony\Component\Security\Core\Role\Role;
$role = new Role('ROLE_ADMIN');
// will echo 'ROLE_ADMIN'
echo $role->getRole();

Большинство токенов наследуют AbstractToken, таким образом роли переданные конструктору будут автоматически преобразованы из строк в объекты класса Role.

Использование менеджера доступа

Слушатели доступа (The Access Listener)

Менеджер доступа может быть использован в любой точке запроса для определения наличия прав у пользователя. Один необязательный, но полезный метод для ограничения доступа на основе шаблона URL — AccessListener, который по-сути является одим из нескольких подписчиков файрвола (см. Подписчики Файрвола). Он обрабатывает каждый запрос, сравнивая его с картой файрвола (см. Файрвол при работе с HTTP запросами).

Он использует карту доступа (должна реализовывать интерфейс AccessMapInterface), которая содержит в себе шаблоны запросов и соответствующий им набор атрибутов. Пользователь должен иметь иметь набор этих атрибутов для доступа к приложению.

<?php
use Symfony\Component\Security\Http\AccessMap;
use Symfony\Component\HttpFoundation\RequestMatcher;
use Symfony\Component\Security\Http\Firewall\AccessListener;
$accessMap = new AccessMap();
$requestMatcher = new RequestMatcher('^/admin');
$accessMap->add($requestMatcher, array('ROLE_ADMIN'));
$accessListener = new AccessListener(
   $securityContext,
   $accessDecisionManager,
   $accessMap,
   $authenticationManager
);

Проверка авторизации

Менеджер доступа доступен во всех частях приложения через метод [isGranted()](http://api.symfony.com/2.6/Symfony/Component/Security/Core/Authorization/AuthorizationChecker.html#isGranted()) класса AuthorizationChecker. Вызов этого метода напрямую передаёт ваш запрос менеджеру доступа:

<?php
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
$authorizationChecker = new AuthorizationChecker(
   $tokenStorage,
   $authenticationManager,
   $accessDecisionManager
);
if (!$authorizationChecker->isGranted('ROLE_ADMIN')) {
   throw new AccessDeniedException();
}