Как добавить функциональность входа в систему "Запомнить меня"

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

Как добавить функциональность входа в систему "Запомнить меня"

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

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# app/config/security.yml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                secret:   '%secret%'
                lifetime: 604800 # 1 week in seconds
                path:     /
                # по умолчанию, фунуция включается при установке
                # флажка в поле формы входа (см. ниже), уберите комментарий
                # из следующей строки, чтобы включать её всегда.
                #always_remember_me: true

Опция secret - это единственная обязательная опция, и она используется для подписания куки "запомнить меня". Распространено ипользование параметра kernel.secret, который определяется с использованием переменной окружения APP_SECRET.

После подключения системы remember_me в конфигурации, есть еще несколько вещей, которые нужно сделать, чтобы "запомнить меня" работало корректно:

  1. Добавить чекбокс для активации "запомнить меня" ;
  2. Использовать аутентифкатор, который поддерживает "запомнить меня" ;
  3. По желанию, сконфигурировать, как хранятся и валидируются куки "запомнить меня" .

После этого, куки "запомнить меня" будет создан после успешной аутентификации. Для некоторых страниц/действий, вы можете принудить пользователя к полной аутентификации (т.е., не через куки "запомнить меня") для большей безопасности.

Note

Настройка remember_me содержит множество настроек для конфигурации куки, созданного этой функцией. См. Настройка куки "запомнить меня", чтобы увидеть полное описание этих настроек.

Активация системы "запомнить меня"

Использование куки "запомнить меня" не всегда правильно (например, вам не стоит использовать его на общем ПК). Поэтому, по умолчанию, Symfony требует, чтобы ваши пользователи выбрали систему "запомнить меня" через параметр запроса.

Этот параметр запроса часто устанавливается через чекбокс в форме входа. Этот чекбокс должен иметь имя _remember_me:

1
2
3
4
5
6
7
8
9
10
11
{# templates/security/login.html.twig #}
<form method="post">
    {# ... your form fields #}

    <label>
        <input type="checkbox" name="_remember_me" checked/>
        Keep me logged in
    </label>

    {# ... #}
</form>

Note

По желанию вы можете сконфигурировать пользовательское имя для этого чекбокса, используя настройку name в разделе remember_me.

Постоянная активация "запомнить меня"

Иногда, вам может захотеться всегда активировать систему "запомнить меня", а не позволять пользователям выбирать это самостоятельно. В таких случаях, вы можете использовать настройку always_remember_me:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
# config/packages/security.yaml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                secret: '%kernel.secret%'
                # ...
                always_remember_me: true

Теперь, ни один параметр запроса не отмечен, а каждая успешная аутентификация будет производить куки "запомнить меня".

Добавление поддержки "запомнить меня" в аутентификатор

Не все методы аутентификации поддерживают "запомнить меня" (например, базовая аутентификация HTTP не имеет такой поддержки). Аутентификатор обозначает поддержку, используя RememberMeBadge в паспорте безопасности .

После входа в систему, вы можете использовать профилировщик безопасности, чтобы увидеть, присутствует ли этот бейдж:

Без этого бейджа, "запомнить меня" не будет активирована (несмотря на все другие настройки).

Добавление поддержки "запомнить меня" в пользовательские аутентификаторы

Когда вы используете пользовательский аутентификатор, вы должны добавить RememberMeBadge вручную:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Service/LoginAuthenticator.php
namespace App\Service;

// ...
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;

class LoginAuthenticator extends AbstractAuthenticator
{
    public function authenticate(Request $request): Passport
    {
        // ...

        return new Passport(
            new UserBadge(...),
            new PasswordCredentials(...),
            [
                new RememberMeBadge(),
            ]
        );
    }
}

Настройка хранения токенов "запомнить меня"

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

Symfony предоставляет два способа валидации токенов "запомнить меня":

Токены, основанные на подписи
По умолчанию, куки "запомнить меня" содержит подпись, осснованную на свойствах пользователя. Если свойства изменяются, подпись изменяется и уже сгенерированные токены больше не считаютя валидными. См. , чтобы узнать больше.
Стойкие токены
Стойкие токены хранят любой сгенерированный токен (например, в базе данных). Это позволяет вам инвалидировать токены, изменяя ряды в базе данных. См. , чтобы узнать больше.

Note

Вы также можете написать собственный обработчик "запомнить меня", создав класс, который расширяет AbstractRememberMeHandler (или реализует RememberMeHandlerInterface). Вы затем можете сконфигурировать этот пользовательский обработчик, сконфигурировав сервис ID в опции service под remember_me.

Использование подписанных токенов "запомнить меня"

По умолчанию, куки "запомнить меня" содержат хеш, который используется для валидации куки. Этот хеш вычисляется, основываясь на сконфигурированных свойствах подписи.

Эти свойства всегда включены в хеш:

  • Идентификатор пользователя (возвращенный getUserIdentifier());
  • Временная отметка истечения срока действия.

Кроме этого, вы можете сконфигурировать пользовательские свойства, используя настройку signature_properties (по умолчанию - password). Свойства извлекаются из объекта пользователя, используя компонент PropertyAccess (например, используя getUpdatedAt() или публичное свойство $updatedAt при использовании updatedAt).

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
# config/packages/security.yaml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                secret: '%kernel.secret%'
                # ...
                signature_properties: ['password', 'updatedAt']

В этом примере, куки "запомнить меня" больше не будет считаться валидным, если updatedAt, пароль или идентификатор пользователя изменяются.

Tip

Свойства подписи позволяют некоторые продвинутые варианты использования, без необходимости настройки хранилища для всех токенов "запомнить меня". Например, вы можете добавить поле forceReloginAt к вашему пользователю и к свойствам подписи. Таким образом, вы можете инвалидировать все токены "запомнить меня" пользователя, изменив эту временную отметку.

Хранение токенов "запомнить меня" в базе данных

Так как токены "запомнить меня" часто долгосрочные, вы можете захотеть хранить их в базе данных, чтобы иметь над ними полный контроль. Symfony поставляется с поддержкой для стойких токенов "запомнить меня".

Эта реализация использует поставщика токенов запомнить меня для хранения и извлечения токенов из базы данных. DoctrineBridge предоставляет поставщика токенов, используя Doctrine.

Вы можете включить поставщика токенов doctrine, используя настройку doctrine:

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

    firewalls:
        main:
            # ...
            remember_me:
                secret: '%kernel.secret%'
                # ...
                token_provider:
                    doctrine: true

Это также сообщит Doctrine создать таблицу для токенов "запомнить меня". Если вы используете DoctrineMigrationsBundle, вы можете создать новую миграцию для этого:

1
2
3
4
$ php bin/console doctrine:migrations:diff

# и опционально запустить миграции локально
$ php bin/console doctrine:migrations:migrate

В других случаях, вы можете использовать команду doctrine:schema:update:

1
2
3
4
5
# получить необходимый код SQL
$ php bin/console doctrine:schema:update --dump-sql

# запустить SQL в вашем клиенте БД или позволить команде запустить его за вас
$ php bin/console doctrine:schema:update --force

Реализация пользовательского поставщика токенов

Вы также можете создать пользовательского поставщика токенов, создав класс, который реализует TokenProviderInterface.

Затем, сконфигурируйте ID сервиса вашего пользовательского поставщика токенов как service:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
# config/packages/security.yaml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                # ...
                token_provider:
                    service: App\Security\RememberMe\CustomTokenProvider

Форсирование повторной аутентификации пользователя перед получением доступа к определённым источникам

Когда пользователь возвращается на ваш сайт, он автоматически аутентифицируется, основываясь на информации, которая хранится в куки "запомнить меня". Это позволяет пользователю получить доступ к защищённым источникам, как будто он действительно был аутентифицирован при посещении сайта.

Однако, в некоторых случаях, вы можете захотеть форсировать, чтобы пользователь действительно повторно проходил аутентификацию до получения доступа к определенным источникам. Например, вы можете не позволять пользователям "запомнить меня" изменять свой пароль. Вы можете сделать это, используя некоторые специальные "атрибуты":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Controller/AccountController.php
// ...

public function accountInfo(): Response
{
    // позволить любому аутентифицированному пользователю - нам не важно, он только что
    // вошел в систему, или вошел через куки "запомнить меня"
    $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

    // ...
}

public function resetPassword(): Response
{
    // требовать, чтобы пользователь вошел в систему во время *этой* сессии
    // если он был в системе только через куки "запомнить меня", он будет
    // перенаправлен на страницу входа
    $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

    // ...
}

Tip

Также есть атрибут IS_REMEMBERED, который предоставляет доступ только когда пользователь аутентифицрован через механизм "запомнить меня".

Настройки куки "запомнить меня"

Конфигурация remember_me содержит множество опций для настраивания куки, созданного системой:

name (значение по умолчанию: REMEMBERME)
Имя куки, использованного для того, чтобы пользователь оставался в системе. Если вы включите функцию remember_me в нескольких брандмауэрах одного приложения, убедитесь, что вы выбрали разные имена для куки в каждом брандмауэре. Иначе вы столкнетесь с множеством проблем безопасности.
lifetime (значение по умолчанию: 31536000 т.e. 1 год в секундах)
Количество секунд, после которого у куки истечет срок действия. Это определяет максимальное время между двумя визитами, чтобы пользователь оставался аутентифицированным.
path (значение по умолчанию: /)
Путь, где используется куки, ассоциированный с этой функцией. По умолчанию, куки будет применен ко всему веб-сайту, но вы можете ограничить его по конкретному разделу (например, /forum, /admin).
domain (значение по умолчанию: null)
Домен, где используется куки, ассоциированный с этой функцией. По умолчанию, куки используют текущий домен, полученный из $_SERVER.
secure (значение по умолчанию: false)
Если true, куки, ассоциирорванный с этой функцией, отправляется пользователю через безопасное соединение HTTPS.
httponly (значение по умолчанию: true)
Если true, у куки, ассоциированному с этой функцией, можно получить доступ только через протокол HTTP. Это означает, что к куки нельзя будет получить доступ через скриптовые языки, такие как JavaScript.
samesite (значение по умолчанию: null)
Если установлена как strict, куки, ассоциированный с этой функцией, не будет отправлен вместе с межсайтовыми запросами, даже при переходе по обычной ссылке.