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

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

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

  • YAML
     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
    
  • XML
     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
    <!-- app/config/security.xml -->
    <?xml version="1.0" encoding="utf-8" ?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <firewall name="main">
                <!-- ... -->
    
                <!-- 604800 is 1 week in seconds -->
                <remember-me
                    secret="%secret%"
                    lifetime="604800"
                    path="/" />
                <!-- по умолчанию, фунуция включается при установке
                     флажка в поле формы входа (см. ниже), добавьте
                     always-remember-me="true", чтобы включать её всегда. -->
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // app/config/security.php
    $container->loadFromExtension('security', array(
        // ...
    
        'firewalls' => array(
            'main' => array(
                // ...
                'remember_me' => array(
                    'secret'   => '%secret%',
                    'lifetime' => 604800, // 1 week in seconds
                    'path'     => '/',
                    // по умолчанию, фунуция включается при установке
                    // флажка в поле формы входа (см. ниже), уберите комментарий
                    // из следующей строки, чтобы включать её всегда.
                    //'always_remember_me' => true,
                ),
            ),
        ),
    ));
    

Брандмауэр remember_me определяет следующие опции конфигурации:

secret (обязательная)
Значение, используемоя для шифрования содержимого cookie. Зачастую используется значение secret, определённое в файле app/config/parameters.yml.
name (значение по умолчанию: REMEMBERME)
Имя cookie, используемого для того, чтобы пользователь оставался в системе. Если вы включаете функцию remember_me в нескольких брандмауэрах одного приложения, убедитесь, что вы выбираете разные именя для cookie каждого брандмауэра. Иначе вы столкнётесь со множеством проблем безопасности.
lifetime (значение по умолчанию: 31536000)
Количество секунд на которое пользователь остаётся в системе. По умолчанию, пользователи выполняют вход на один год.
path (значение по умолчанию: /)
Путь, где используется cookie, связанный с этой функцией. По умолчанию, cookie будет применён ко всему сайту, но вы можете ограничить его конкретным разделом (например, /forum, /admin).
domain (значение по умолчанию: null)
Домен, где используется cookie, связанный с этой функцией. По умолчанию, cookie используют текущий домен, полученный из $_SERVER.
secure (значение по умолчанию: false)
Если значение - true, то cookie, связанный с этой функцией, отправляется пользователю через защищённое HTTPS-соединение.
httponly (значение по умолчанию: true)
Если значение - true, то cookie, связанный с этой функцией, доступен только через HTTP-протокол. Это означает, что cookie не будет доступен через скриптовые языки, такие как JavaScript.
remember_me_parameter (значение по умолчанию: _remember_me)
Имя поля формы, отмеченного для того, чтобы решить нужно ли активировать функцию "Запомнить меня". Продолжайте читать эту статью, чтобы узнать, как включить эту функцию при определённых условиях.
always_remember_me (значение по умолчанию: false)
Если значение - true, то значение``remember_me_parameter`` игнорируется, и функция "Запомнить меня" всегда активирована, независимо от желания конечного пользователя.
token_provider (значение по умолчанию: null)
Определяет, какой id сервиса поставщика токенов использовать. По умолчанию, токены хранятся в cookie. Например, вы можете захотеть хранить токен в БД, чтобы не иметь (хешированную) версию пароля в cookie. DoctrineBridge поставляется с Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider, который вы можете использовать.

Принуждение пользователя отказаться от функции "Запомнить меня"

Хорошей идеей будет предоставить пользователю выбор - использовать или не использовать функцию "Запомнить меня", так как она не всегда будет уместна. Обычный способ решения этого - добавить поле с флажком в форму входа. Если этому полю дать имя _remember_me (или имя, которое вы сконфигурировали, используя remember_me_parameter), cookie будет автоматически установлен, когда флажок будет установлен и пользователь успешно выполнит вход. Поэтому, ваша конкретная форма входа в систему может выглядеть так:

  • Twig
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    {# app/Resources/views/security/login.html.twig #}
    {% if error %}
        <div>{{ error.message }}</div>
    {% endif %}
    
    <form action="{{ path('login') }}" method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="_username" value="{{ last_username }}" />
    
        <label for="password">Password:</label>
        <input type="password" id="password" name="_password" />
    
        <input type="checkbox" id="remember_me" name="_remember_me" checked />
        <label for="remember_me">Keep me logged in</label>
    
        <input type="submit" name="login" />
    </form>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- app/Resources/views/security/login.html.php -->
    <?php if ($error): ?>
        <div><?php echo $error->getMessage() ?></div>
    <?php endif ?>
    
    <form action="<?php echo $view['router']->path('login') ?>" method="post">
        <label for="username">Username:</label>
        <input type="text" id="username"
               name="_username" value="<?php echo $last_username ?>" />
    
        <label for="password">Password:</label>
        <input type="password" id="password" name="_password" />
    
        <input type="checkbox" id="remember_me" name="_remember_me" checked />
        <label for="remember_me">Keep me logged in</label>
    
        <input type="submit" name="login" />
    </form>
    

Пользователь будет автоматически выполнять вход при последующих визитах, пока cookie будет валиден.

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

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

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

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

IS_AUTHENTICATED_ANONYMOUSLY
Автоматически приписывается пользователю, который находится в защищённой брандмауэром части сайта, но на самом деле не выполнил вход. Это возможно только если был разрешён анонимный вход.
IS_AUTHENTICATED_REMEMBERED
Автоматически приписывается пользователю, который был аутентифицирован через cookie "запомнить меня".
IS_AUTHENTICATED_FULLY
Автоматичеки приписывается пользователю, который предоставил детали входа в систему во время текущей сессии.

Вы можете использовать их для контроля доступа кроме ясно прдписанных ролей.

Note

Если у вас есть роль IS_AUTHENTICATED_REMEMBERED, то у вас также есть роль IS_AUTHENTICATED_ANONYMOUSLY. Если у вас есть роль IS_AUTHENTICATED_FULLY, то у вас также есть две другие роли. Другими словами, эти роли представляют собой три уровня увеличивающейся "силы" аутентификации.

Вы можете использовать эти дополнительные роли для более чувствительного контроля доступа к частям сайта. Например, вы можете хотеть, чтобы пользователь мог просматривать свою учётную запись в /account, когда он аутентифицирован через cookie, но для изменения деталей учётной записи требовать детали входа в систему. Вы можете сделать это, обезопасив некоторые действия контроллера, используя эти роли. Действие редактирования в контроллере можно обезопасить с помощью контекста сервиса.

В следующем примере, действие разрешено только, если у пользователя есть роль IS_AUTHENTICATED_FULLY.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException

// ...
public function editAction()
{
    $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

    // ...
}

Если ваше приложение основывается на стандартной версии Symfony, вы также можете обезопасить свой контроллер, используя аннотации:

1
2
3
4
5
6
7
8
9
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Security("is_granted('IS_AUTHENTICATED_FULLY')")
 */
public function editAction($name)
{
    // ...
}

Tip

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

  • Если не аутентифицированный (или анонимно аутентифицированный) пользователь попробует получить доступ к учётной записи, его попросят аутентифицироваться.
  • Когда пользователь вводит своё имя и пароль (и предполагая, что по вашей конфигурации пользователь получит роль ROLE_USER), то он получает роль IS_AUTHENTICATED_FULLY и будет иметь доступ к любой странице учётной записи, включая контрролер editAction().
  • Если сессия пользователя заканчивается, то когда пользователь вернётся на сайт, он будет иметь доступ к каждой странице учётной записи, кроме страницы редактирования, без запроса на повторную аутентификацию. Однако, если он попробует получить доступ к контроллеру editAction(), он будет вынужден повторно аутентифицироваться, так как он ещё не полностью аутентифицирован.

Чтобы узнать больше о защите сервисов или методов таким образом, смотрите How to Secure any Service or Method in your Application.

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