Аутентификация

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

class SomeAuthenticationListener implements ListenerInterface
{
    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @var AuthenticationManagerInterface
     */
    private $authenticationManager;

    /**
     * @var string Uniquely identifies the secured area
     */
    private $providerKey;

    // ...

    public function handle(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        $username = ...;
        $password = ...;

        $unauthenticatedToken = new UsernamePasswordToken(
            $username,
            $password,
            $this->providerKey
        );

        $authenticatedToken = $this
            ->authenticationManager
            ->authenticate($unauthenticatedToken);

        $this->tokenStorage->setToken($authenticatedToken);
    }
}

Note

Токен может быть любым классом, если он реализует TokenInterface.

Менеджер аутентификации

Менеджер аутентификации по умолчанию является экземпляром AuthenticationProviderManager:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

// экземпляры Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface
$providers = array(...);

$authenticationManager = new AuthenticationProviderManager($providers);

try {
    $authenticatedToken = $authenticationManager
        ->authenticate($unauthenticatedToken);
} catch (AuthenticationException $exception) {
    // аутентификация неудачна
}

При инстанциировании, AuthenticationProviderManager получает несколько поставщиков аутентификации, каждый из которых поддерживает разные виды токенов.

Note

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

Поставщики аутентификации

Каждый поставщик (так как он реализует AuthenticationProviderInterface) имеет метод supports(), по которому AuthenticationProviderManager может определить, поддерживается ли данный токен. Если это так, то менеджер вызывает метод поставщика authenticate(). Этот метод должен вернуть аутнетифицированный токен или вызвать AuthenticationException (или любое другео исключение, расширяющее его).

Аутентификация пользователей по имени и паролю

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

Этот функционал предоставлен DaoAuthenticationProvider. Он извлекает данные пользователя из:class:Symfony\Component\Security\Core\User\UserProviderInterface, использует PasswordEncoderInterface для создания хеша пароля и возвращает аутентифицированный токен, если пароль валиден:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\User\UserChecker;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;

$userProvider = new InMemoryUserProvider(
    array(
        'admin' => array(
            // пароль "foo"
            'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==',
            'roles'    => array('ROLE_ADMIN'),
        ),
    )
);

// для дополнительных проверок: подключен/закрыт/истёк ли срок действия аккаунта
$userChecker = new UserChecker();

// массив кодировщиков паролей (см. ниже)
$encoderFactory = new EncoderFactory(...);

$daoProvider = new DaoAuthenticationProvider(
    $userProvider,
    $userChecker,
    'secured_area',
    $encoderFactory
);

$daoProvider->authenticate($unauthenticatedToken);

Note

Пример выше демонстрирует использование поставщика пользователей "оперативной памяти", но вы можете использовать любого другого поставщика пользователей, если он реализует ChainUserProvider.

Фабрика кодировщиков паролей

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use Acme\Entity\LegacyUser;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\Security\Core\User\User;

$defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
$weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1);

$encoders = array(
    User::class       => $defaultEncoder,
    LegacyUser::class => $weakEncoder,
    // ...
);
$encoderFactory = new EncoderFactory($encoders);

Каждый кодировщик должен реализовывать PasswordEncoderInterface или быть массивом с class и ключом arguments, который позволяется фабрике кодировщиков конструировать кодировщик только по необходимости.

Создание пользовательского кодировщика паролей

Существует множество встроенных кодировщиков паролей. Но если вам нужно создать собственный, он должен следовать таким правилам:

  1. Класс должен реализовывать PasswordEncoderInterface;

  2. Реализация encodePassword() и isPasswordValid() должна вначале убедиться, что пароль не слишком длинный, т.е. длина пароля не превышает 4096 символов. Это из соображений безопасности (см. CVE-2013-5750), и вы можете использовать метод isPasswordTooLong() для такой проверки:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
    use Symfony\Component\Security\Core\Exception\BadCredentialsException;
    
    class FoobarEncoder extends BasePasswordEncoder
    {
        public function encodePassword($raw, $salt)
        {
            if ($this->isPasswordTooLong($raw)) {
                throw new BadCredentialsException('Invalid password.');
            }
    
            // ...
        }
    
        public function isPasswordValid($encoded, $raw, $salt)
        {
            if ($this->isPasswordTooLong($raw)) {
                return false;
            }
    
            // ...
        }
    }
    

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

Когда метод getEncoder() фабрики кодировщиков паролей вызывается с объектом пользователя в качестве первого аргумента, он будет возвращать кодировщик типа PasswordEncoderInterface, который должен быть использован для шифрованя пароля пользователя:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// экземпляр Acme\Entity\LegacyUser
$user = ...;

// отправленный пароль, например, при регистрации
$plainPassword = ...;

$encoder = $encoderFactory->getEncoder($user);

// возвращает $weakEncoder (см. выше)
$encodedPassword = $encoder->encodePassword($plainPassword, $user->getSalt());

$user->setPassword($encodedPassword);

// ... сохраните пользователя

Теперь, когда вы хотите проверить, является ли отправленный пароль (например, при попытке входа в систему) правильным, вы можете использовать:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// извлечь Acme\Entity\LegacyUser
$user = ...;

// отпавленный пароль, например, из формы входа
$plainPassword = ...;

$validPassword = $encoder->isPasswordValid(
    $user->getPassword(), // зашифрованный пароль
    $plainPassword,       // отправленный пароль
    $user->getSalt()
);

События аутентификации

Компонент безопасности предоставляет 4 связанных с аутентификацией события:

Газвание Константа события Аргумент, переданный слушателю
security.authentication.success AuthenticationEvents::AUTHENTICATION_SUCCESS AuthenticationEvent
security.authentication.failure AuthenticationEvents::AUTHENTICATION_FAILURE AuthenticationFailureEvent
security.interactive_login SecurityEvents::INTERACTIVE_LOGIN InteractiveLoginEvent
security.switch_user SecurityEvents::SWITCH_USER SwitchUserEvent

События удачной и неудачной аутентификации

Когда поставщик аутентифицирует пользователя, запускается событие security.authentication.success. Но будьте осторожны - это событие будет запускаться, к примеру, при каждом запросе, если у вас аутентификация основывается на сессии. См. security.interactive_login ниже, если вам нужно сделать что-то, когда пользователь действительно выполняет вход.

Когда поставщик пробует выполнть аутентификацию, но терпит неудачу (т.е. вызывает AuthenticationException), запускается событие security.authentication.failure. Вы можете слушать например событие security.authentication.failure для того, чтобы вести логи неудачных попыток входа.

События безопасности

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

  • аутентификация, основанная на вашей сессии
  • аутентификация, используя базовый HTTP заголовок.

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

Событие security.switch_user запускается каждый раз, когда вы активируете слушателя брандмауэра switch_user.

Чтобы узнать больше о переключении пользователей, см. How to Impersonate a User.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.