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

Дата обновления перевода 2023-05-26

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

Коли запит вказує на безпечну область, а один зі слухачів з карти брандмауера може витягти особисті дані користувача з поточного об'єкта 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
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

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

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

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

    // ...

    public function __invoke(RequestEvent $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. Він витягує дані користувача з UserProviderInterface, використовує UserPasswordHasherInterface для створення хешу пароля і повертає аутентифікований токен, якщо пароль валідний:

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
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\UserChecker;

// Класс 'InMemoryUser' был представлен в Symfony 5.3.
// В предыдущих версиях он назывался 'User'
$userProvider = new InMemoryUserProvider(
    [
        'admin' => [
            // пароль - "foo"
            'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==',
            'roles'    => ['ROLE_ADMIN'],
        ],
    ]
);

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

// масив кодувальників паролів (див. нижче)
$hasherFactory = new PasswordHasherFactoryInterface(...);

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

$daoProvider->authenticate($unauthenticatedToken);

Note

Приклад вище демонструє використання постачальника користувачів "оперативної пам'яті", але ви можете використовувати будь-якого іншого постачальника користувачів, якщо він реалізує UserProviderInterface Також можливо дозволити кільком постачальникам користувачів знайти дані користувача користувача, використовуючи ChainUserProvider.

Фабрика кодувальників паролів

DaoAuthenticationProvider використовує фабрику кодувальників для створення кодувальника паролів для заданого типу користувача. Це дозволяє вам використовувати різні стратегії шифрування для різних типів користувачів. За замовчуванням, PasswordHasherFactory отримує масив кодувальників:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Acme\Entity\LegacyUser;
use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher;
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
use Symfony\Component\Security\Core\User\InMemoryUser;

$defaultHasher = new MessageDigestPasswordHasher('sha512', true, 5000);
$weakHasher = new MessageDigestPasswordHasher('md5', true, 1);

$hashers = [
    InMemoryUser::class => $defaultHasher,
    LegacyUser::class   => $weakHasher,
    // ...
];
$hasherFactory = new PasswordHasherFactory($hashers);

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

Створення користувацького кодувальника паролів

Існує безліч вбудованих кодувальників паролів. Але якщо вам потрібно створити власний, він повинен слідувати таким правилам:

  1. Клас повинен реалізовувати UserPasswordHasherInterface (ви також можете розширити UserPasswordHasher);
  2. Реалізація hashPassword() і 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
    24
    25
    26
    use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait;
    use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher;
    use Symfony\Component\Security\Core\Exception\BadCredentialsException;
    
    class FoobarHasher extends UserPasswordHasher
    {
        use CheckPasswordLengthTrait;
    
        public function hashPassword(UserInterface $user, string $plainPassword): string
        {
            if ($this->isPasswordTooLong($user->getPassword())) {
                throw new BadCredentialsException('Invalid password.');
            }
    
            // ...
        }
    
        public function isPasswordValid(UserInterface $user, string $plainPassword)
        {
            if ($this->isPasswordTooLong($user->getPassword())) {
                return false;
            }
    
            // ...
        }
    }

Використання кодувальників паролів

Коли метод getPasswordHasher() фабрики кодувальників паролів викликається з об'єктом користувача в якості першого аргументу, він повертатиме кодувальник типу PasswordHasherInterface, який має бути використаний для шифрування пароля користувача:

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

// відправлений пароль, наприклад, при реєстрації
$plainPassword = ...;

$hasher = $hasherFactory->getPasswordHasher($user);

// повертає $weakHasher (см. выше)
$hashedPassword = $hasher->hashPassword($user, $plainPassword);

$user->setPassword($hashedPassword);

// ... зберегти користувача

Тепер, коли ви хочете перевірити, чи є надісланий пароль (наприклад, при спробі входу в систему) правильним, ви можете використовувати:

1
2
3
4
5
6
7
// витягти Acme\Entity\LegacyUser
$user = ...;

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

$validPassword = $hasher->isPasswordValid($user, $plainPassword);

Події аутентифікації

Компонент Security надає такі події, повʼязані з аутентифікацією:

?????. ????????? ?????. ????????, ?? ???????? ???????
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.logout_on_change Symfony\Component\Security\Http\Event\DeauthenticatedEvent::class DeauthenticatedEvent

Події вдалої та невдалої аутентифікації

Коли постачальник аутентифікує користувача, запускається подія security.authentication.success. Але будьте обережні - ця подія буде запускатися, наприклад, при кожному запиті, якщо у вас аутентифікація ґрунтується на сесії. Див. security.interactive_login нижче, якщо вам потрібно зробити щось, коли користувач справді виконує вхід.

5.4

Опція always_authenticate_before_granting застаріла в Symfony 5.4, і буде видалена в 6.0.

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

Події безпеки

Подія security.interactive_login викликається після того, як користувач виконав активний вхід на ваш сайт. Важливо відрізняти цю дію від неінтерактивних методів аутентифікації на кшталт:

  • аутентифікація, заснована на вашій сесії
  • аутентифікація, використовуючи базовий HTTP заголовок.

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

Подія security.switch_user запускається щоразу, коли ви активуєте слухача брандмауера switch_user.

Подія Symfony\Component\Security\Http\Event\DeauthenticatedEvent викликається, коли токен був деаутентифікований через зміни користувача, вона може допомогти вам з деякими завдання з прибирання.

See also

Щоб дізнатися більше про перемикання користувачів, див. Як імперсонувати користувача.