Как создать пользовательский аутентификатор пароля

Как создать пользовательский аутентификатор пароля

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:

  • YAML
  • XML
  • PHP
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, указывающим на новый сервис. Для деталей, смотрите Справочник конфигурацим Безопасности (SecurityBundle).

Если создание формы входа в общем для вас в новинку, или если вы не понимаете опции check_path или login_path, смотрите Как настроить ответы аутентификатора формы входа.