Як створити користувацький аутентифікатор пароля

Дата оновлення перекладу 2023-06-15

Як створити користувацький аутентифікатор пароля

Tip

Подивіться Система користувацької аутентифікації з Guard (приклад API токена), щоб дізнатися про легший та гнучкіший спосіб домогтися завдань користувацької аутентифікації, на кшталт цієї.

Уявіть, що ви хочете дозволити доступ до вашої сторінки тільки в проміжку з 2 до 4 години дня за світовим часом. У цій статті, ви дізнаєтеся, як зробити це у формі входу (тобто там, де ваш користувач надсилає своє ім'я користувача та пароль).

Аутентифікатор пароля

Для початку створіть новий клас, що реалізує SimpleFormAuthenticatorInterface. Зрештою, це дасть вам змогу створювати користувацьку логіку для аутентифікації користувача:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// src/Security/TimeAuthenticator.php
namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimpleFormAuthenticatorInterface;

class TimeAuthenticator implements SimpleFormAuthenticatorInterface
{
    private $encoder;

    public function __construct(UserPasswordEncoderInterface $encoder)
    {
        $this->encoder = $encoder;
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        try {
            $user = $userProvider->loadUserByUsername($token->getUsername());
        } catch (UsernameNotFoundException $e) {
            // УВАГА: це повідомлення буде повернено клієнту
            // (тому не розміщуйте тут рядки недовірених/хибних повідомлень)
            throw new CustomUserMessageAuthenticationException('Invalid username or password');
        }

        $passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());

        if ($passwordValid) {
            $currentHour = date('G');
            if ($currentHour < 14 || $currentHour > 16) {
                // УВАГА: це повідомлення буде повернено клієнту
                // (тому не розміщуйте тут рядки недовірених/хибних повідомлень)
                throw new CustomUserMessageAuthenticationException(
                    'Вы можете выполнять вход только с 2 до 4!',
                    array(), // Данные сообщения
                    412 // Условие HTTP 412 неудачно
                );
            }

            return new UsernamePasswordToken(
                $user,
                $user->getPassword(),
                $providerKey,
                $user->getRoles()
            );
        }

        // УВАГА: це повідомлення буде повернено клієнту
        // (тому не розміщуйте тут рядки недовірених/хибних повідомлень)
        throw new CustomUserMessageAuthenticationException('Invalid username or password');
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof UsernamePasswordToken
            && $token->getProviderKey() === $providerKey;
    }

    public function createToken(Request $request, $username, $password, $providerKey)
    {
        return new UsernamePasswordToken($username, $password, $providerKey);
    }
}

Як це працює

Чудово! Тепер вам просто потрібно налаштувати . Але спочатку ви можете дізнатися більше про те, що робить кожний метод у цьому класі.

1) createToken

Коли Symfony починає обробляти запит, викликається createToken(), де ви створюєте об'єкт TokenInterface, який містить потрібну вам інформацію для аутентифікації користувача (наприклад, ім'я користувача і пароль) в authenticateToken() .

Будь-який об'єкт токена, який ви створите тут, буде переданий вам пізніше в authenticateToken().

2) supportsToken

Після того, як Symfony викличе createToken(), вона викличе supportsToken() у вашому класі (і будь-яких інших слухачів аутентифікації), щоб з'ясувати, хто має працювати з токеном. Це просто спосіб дозволити кільком механізмам автентифікації бути використаними для одного брендмауера (отже, ви, наприклад, можете спочатку спробувати автентифікувати користувача через сертифікат або API-ключ, а як резерв - через форму входу).

В основному, вам потрібно просто переконатися, що цей метод повертає "true" для токена, який був створений createToken(). Ваша логіка, швидше за все, має виглядати так само, як цей приклад.

3) authenticateToken

Якщо supportsToken() повертає true, Symfony викличе authenticateToken(). Ваша робота тут полягає в перевірці того, чи дозволено токену виконувати вхід; спочатку отримайте об'єкт User через постачальника користувача, а потім, перевірте пароль і поточний час.

Note

"Процес" того, як ви отримаєте об'єкт User і визначите, чи валідний токен (наприклад, перевірка пароля), може відрізнятися залежно від ваших вимог.

Зрештою, ваше завдання - повернути новий об'єкт токена, який буде "аутентифікований" (тобто матиме щонайменше 1 встановлену роль), і який має всередині об'єкт User.

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

1
$passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());

Це сервіс, який уже доступний у Symfony і який використовує алгоритм, сконфігурований у конфігурації безпеки (наприклад, security.yaml) під ключем encoders. Нижче ви побачите, як впровадити це в TimeAuthenticator.

Конфігурація

Тепер, переконайтеся в тому, що ваш TimeAuthenticator зареєстрований, як сервіс. Якщо ви використовуєте конфігурацію services.yaml за замовчуванням , то це відбувається автоматично.

Нарешті, активуйте сервіс у розділі firewalls конфігурації безпеки, використовуючи ключ simple_form:

1
2
3
4
5
6
7
8
9
10
11
12
# config/packages/security.yaml
security:
    # ...

    firewalls:
        secured_area:
            pattern: ^/admin
            # ...
            simple_form:
                authenticator: App\Security\TimeAuthenticator
                check_path:    login_check
                login_path:    login

Ключ simple_form має ті самі опції, що й звичайна опція form_login, але з додатковим ключем authenticator, що вказує на новий сервіс. Для деталей, дивіться .

Якщо створення форми входу загалом для вас у новинку, або якщо ви не розумієте опції check_path або login_path, дивіться Як налаштувати відповіді аутентифікатора форми входу.