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

Tip

Посмотрите How to Create a Custom Authentication System with Guard, чтобы узнать о более лёгком и гибком способе добиться задач пользовательской аутентификации, вроде этой.

Представьте, что вы хотите позволить доступ к вашей странице только в промежутке с 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);
    }
}

Как это работает

Отлично! Теперь вам просто нужно настроить Configuration. Но вначале, вы можете узнать больше о том, что делает каждый метод в этом классе.

1) createToken

Когда Symfony начинает обрабатывать запрос, вызывается createToken(), где вы создаёте объект TokenInterface, который содержит нужную вам информацию для аутентификации пользователя (например, имя пользователя и пароль) в authenticateToken() .

Любой объект токена, который вы создадите здесь, будет передан вам позже в authenticateToken().

2) supportsToken

After Symfony calls createToken(), it will then call supportsToken() on your class (and any other authentication listeners) to figure out who should handle the token. This is just a way to allow several authentication mechanisms to be used for the same firewall (that way, you can for instance first try to authenticate the user via a certificate or an API key and fall back to a form login).

Mostly, you just need to make sure that this method returns true for a token that has been created by createToken(). Your logic should probably look exactly like this example.

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
     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
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- config/packages/security.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
        <config>
            <!-- ... -->
    
            <firewall name="secured_area"
                pattern="^/admin"
                >
                <simple-form authenticator="App\Security\TimeAuthenticator"
                    check-path="login_check"
                    login-path="login"
                />
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // config/packages/security.php
    
    // ...
    use App\Security\TimeAuthenticator;
    
    $container->loadFromExtension('security', array(
        'firewalls' => array(
            'secured_area'    => array(
                'pattern'     => '^/admin',
                'simple_form' => array(
                    'provider'      => ...,
                    'authenticator' => App\Security\TimeAuthenticator::class,
                    'check_path'    => 'login_check',
                    'login_path'    => 'login',
                ),
            ),
        ),
    ));
    

Ключ simple_form имеет те же опции, что и обычная опция form_login, но с дополнительным ключом authenticator, указывающим на новый сервис. Для деталей, смотрите Form Login Configuration.

Если создание формы входа в общем для вас в новинку, или если вы не понимаете опции check_path или login_path, смотрите How to Customize Redirect After Form Login.

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