Как построить традиционную форму входа в систему

Tip

Если вам нужна форма входа, и вы храните пользователей в какой-то БД, то вам стоит рассмотреть использование FOSUserBundle, который помогает вам строить ваш объект User и предоставляет множество маршрутов и контроллеров для общих задач вроде выполнения входа, регистрации и забытого пароля.

В этой записи вы построите традиционную форму входа в систему. Конечно, когда пользователь выполняет вход, вы можете загружать ваших пользователей откуда угодно - например, из БД. Смотрите, B) Configuring how Users are Loaded, чтобы узнать больше.

Для начала, включите форму входа в систему под вашим брандмауэром:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    # app/config/security.yml
    security:
        # ...
    
        firewalls:
            main:
                anonymous: ~
                form_login:
                    login_path: login
                    check_path: login
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- 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">
                <anonymous />
                <form-login login-path="login" check-path="login" />
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // app/config/security.php
    $container->loadFromExtension('security', array(
        'firewalls' => array(
            'main' => array(
                'anonymous'  => null,
                'form_login' => array(
                    'login_path' => 'login',
                    'check_path' => 'login',
                ),
            ),
        ),
    ));
    

Tip

login_path и check_path также могут быть именами маршрута (но не не могут иметь обязательные подстановочные знаки - например, /login/{foo} где foo не имеет значения по умолчанию).

Теперь, когда система безопасности инициирует процесс аутентификации, она будет перенаправлять пользователя на форму выполнения входа /login. Реализация этой формы входа в систему - ваша работа. Для начала, создайте новый SecurityController внутри пакета:

1
2
3
4
5
6
7
8
// src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class SecurityController extends Controller
{
}

Далее, сконфигурируйте маршрут, который вы ранее использовали в вашей конфигурации form_login (login):

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/AppBundle/Controller/SecurityController.php
    
    // ...
    use Symfony\Component\HttpFoundation\Request;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    
    class SecurityController extends Controller
    {
        /**
         * @Route("/login", name="login")
         */
        public function loginAction(Request $request)
        {
        }
    }
    
  • YAML
    1
    2
    3
    4
    # app/config/routing.yml
    login:
        path:     /login
        defaults: { _controller: AppBundle:Security:login }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="login" path="/login">
            <default key="_controller">AppBundle:Security:login</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // app/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('login', new Route('/login', array(
        '_controller' => 'AppBundle:Security:login',
    )));
    
    return $collection;
    

Отлично! Далее, добавьте логику к loginAction(), которая отображает форму входа:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// src/AppBundle/Controller/SecurityController.php
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

public function loginAction(Request $request, AuthenticationUtils $authUtils)
{
    // получить ошибку входа, если она есть
    $error = $authUtils->getLastAuthenticationError();

    // последнее имя пользователя, введенное пользователем
    $lastUsername = $authUtils->getLastUsername();

    return $this->render('security/login.html.twig', array(
        'last_username' => $lastUsername,
        'error'         => $error,
    ));
}

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

Другими словами, ваша работа - отобразить форму входа и любые ошибки входа, которые могли возникнуть, но система безопасности сама заботится о проверке отправленных имени пользователя и пароля, а также об аутентификации пользователя.

Finally, create the template:

  • Twig
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    {# app/Resources/views/security/login.html.twig #}
    {# ... вы скорее всего расширите ваш базовый шаблон, вроде base.html.twig #}
    
    {% if error %}
        <div>{{ error.messageKey|trans(error.messageData, 'security') }}</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" />
    
        {#
            Если вы хотите контролировать URL, на который будет перенаправлен
            пользователь при успешном входе (больше деталей ниже)
            <input type="hidden" name="_target_path" value="/account" />
        #}
    
        <button type="submit">login</button>
    </form>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- src/AppBundle/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" />
    
        <!--
            Если вы хотите контролировать URL, на который будет перенаправлен
            пользователь при успешном входе (больше деталей ниже)
            <input type="hidden" name="_target_path" value="/account" />
        -->
    
        <button type="submit">login</button>
    </form>
    

Tip

Переменная error передаётся в шаблон, как экземпляр AuthenticationException. Он может содержать болше информации - даже секретной - об ошибке аутентификации, так что используйте его с умом!

Форма может выглядеть как угодно, но обычно она следует некоторым договорённостям:

  • Элемент <form> отправляет запрос POST к маршруту login,так как это то, что вы сконфигурировали под ключом form_login в security.yml;
  • Поле имени пользователя имеет имя _username, а поле пароля - _password.

Tip

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

Caution

Эта форма входа в систему на данный момент не защищена от CSRF-атак. Прочтите /security/csrf_in_login_form, чтобы узнать, как защитить вашу форму.

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

Повторим весь процесс:

  1. Пользователь пытается получить доступ к защищённому ресурсу;
  2. Брандмауэр инициирует процесс аутентификации, перенаправляя пользователя к форме входа в систему (/login);
  3. Страница /login отображает форму входа через маршрут и контроллер, созданные в этом примере;
  4. Пользователь отправляет форму входа в /login;
  5. Система безопасности принимает запрос, проверяет отправленную пользователем аккредитацию, аутентифицирует пользователя, если всё верно, и отправляет пользователя обратно к форме входа - если нет.

Перенаправление после успешного входа

Если отправленная аккредитация верна, пользователь будет перенаправлен на страницу, которая была запрошена первоначально (например, /admin/foo). Если пользователь изначально зашёл на страницу входа в систему, он будет перенаправлен на домашнюю страницу. Это можно настроить, и вы можете, например, перенаправить пользователя по конкретному URL.

Чтобы узнать больше деталей об этом и о том, как настроить процесс формы входа в систему в общем, смотрите How to Customize Redirect After Form Login.

Избегайте распространённых ловушек

При установке вашей формы входа в систему, будьте осторожны с наиболее распространёнными ловушками.

1. Создавайте правильные маршруты

Во-первых, убедитесь, что вы правильно определили маршрут /login, и что он соответствует значениям конфигурации login_path и check_path. Неправильная конфигурация тут может означать, что вы будете перенаправлены на страницу 404 вместо страницы входа, или что отправка формы входа ничего не будет делать (вы просто будете видеть форму входа снова и снова).

2. Убедитесь, что страница входа не защищена (цикл перенаправления!)

Также, убедитесь в том, что страница входа доступна анонимным пользователям. Например, следующая конфигурация, требующая роль ROLE_ADMIN для всех URL (включая URL /login), будет вызывать цикл перенаправления:

  • YAML
    1
    2
    3
    4
    5
    # app/config/security.yml
    
    # ...
    access_control:
        - { path: ^/, roles: ROLE_ADMIN }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- app/config/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>
            <!-- ... -->
            <rule path="^/" role="ROLE_ADMIN" />
        </config>
    </srv:container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    // app/config/security.php
    
    // ...
    'access_control' => array(
        array('path' => '^/', 'role' => 'ROLE_ADMIN'),
    ),
    

Добавления управления доступа, совпадающего с /login/* и не требующего аутентификации, исправит эту проблему:

  • YAML
    1
    2
    3
    4
    5
    6
    # app/config/security.yml
    
    # ...
    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: ROLE_ADMIN }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <!-- app/config/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>
            <!-- ... -->
            <rule path="^/login" role="IS_AUTHENTICATED_ANONYMOUSLY" />
            <rule path="^/" role="ROLE_ADMIN" />
        </config>
    </srv:container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    // app/config/security.php
    
    // ...
    'access_control' => array(
        array('path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'),
        array('path' => '^/', 'role' => 'ROLE_ADMIN'),
    ),
    

3. Убедитесь, что check_path находится за брандмауэром

Далее, убедитесь, что ваш URL check_path (например, /login) находится за брандмауэром, который вы используете для вашей формы входа (в этом примере, один брандмауэр совпадает со всеми URL, включая /login). Если /login не совпадает ни с одним брандмауэром, вы получите исключение "Невозможно найти контроллер для пути "/login"``.

4. Несколько брандмауэров не имеют общий контекст безопасности

Если вы используете несколько брандмауэров, а аутентифицируете по одному из них, то вы не будете аутентифицировать по всем остальным автоматически. Разные брандмауэры - это как разные системы безопасности. Чтобы сделать это, вам нужно ясно указать одинаковый Контекст брандмауэра для разных брандмауэров. Но обычно для большинства приложений одного брандмауэра достаточно.

5. Маршрутизация страниц ошибок не покрывается брандмауэрами

Так как маршрутизация проходит до безопасности, страницы ошибок 404 не покрываются ни одним брандмауэром. Это означает, что вы не сможете проверить безопасность или даже получить доступ к объекту пользователя на этих страницах. Смотрите See How to Customize Error Pages, чтобы узнать больше.

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