Дата обновления перевода 2021-06-09

Безопасность

Screencast

Предпочитаете видео-уроки? Посмотрите Symfony Security screencast series.

Система безопасности Symfony невероятно мощная, но в то же время может быть сложной в установке. Не волнуйтесь! В этой статье вы научитесь устанавливать безопасность вашего приложения шаг за шагом:

  1. Установка поддержки безопасности;
  2. Создайте свой класс Пользователя;
  3. Аутентификация и файерволлы;
  4. Отказ в доступе к вашему приложению (авторизация);
  5. Извлчение текущего объекта Пользователя.

Несколько других важных тем обсуждаются после этого.

1) Установка

В приложениях, использующих Symfony Flex, выполните эту команду, чтобы установить функцию безопасности перед её использованием:

1
$ composer require symfony/security-bundle

Tip

Новая экспериментальная Безопасность была представлена в Symfony 5.1, которая в итоге заменить безопасность в Symfony 6.0. Эта система почти полностью обратно совместима с текущей безопасностью Symfony, добавьте эту строку в вашу конфигруацию безопасности, чтобы начать ее использовать:

  • YAML
    1
    2
    3
    4
    # config/packages/security.yaml
    security:
        enable_authenticator_manager: true
        # ...
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <!-- 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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <config enable-authenticator-manager="true">
            <!-- ... -->
        </config>
    </srv:container>
    
  • PHP
    1
    2
    3
    4
    5
    // config/packages/security.php
    $container->loadFromExtension('security', [
        'enable_authenticator_manager' => true,
        // ...
    ]);
    

2a) Cоздайте ваш класс Пользователя

Независимо от того, как вы будете проводить аутентификацию (например, через форму входа в систему или API-токены), или где будут храниться ваши данные пользователя (база данных, единая точка входа), следующий шаг всегда будет одинаков: создание класса “Пользователя”. Самый простой способ - использовать MakerBundle.

Давайте предположим, что вы хотите хранить ваши данные пользователя в базе данных с Doctrine:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ php bin/console make:user

Имя класса пользователя безопасности (например, User) [User]:
> User

Вы хотите хранить данные пользователей в базе данных (через Doctrine)? (да/нет) [yes]:
> yes

Введите имя свойства, которое будет уникальным "отображаемым" именем для пользователя (например,
email, username, uuid [email]
> email

Нужно ли этому приложению хешировать/проверять пароли пользователей? (да/нет) [yes]:
> yes

created: src/Entity/User.php
created: src/Repository/UserRepository.php
updated: src/Entity/User.php
updated: config/packages/security.yaml

Вот и все! Команда задает несколько вопросов, чтобы сгенерировать именно то, что вам нужно. Наиболее важным является сам файл User.php. Единственное правило о вашем классе User - он должен реализовать UserInterface. Вы можете добавить любые другие поля или логику, которые вам нужны. Если ваш класс User является сущностью (как в этом примере), вы можете использовать команду make:entity, чтобы добавить больше полей. Также, убедитесь, что вы создали и запустили миграцию для новой сущности:

1
2
$ php bin/console make:migration
$ php bin/console doctrine:migrations:migrate

2b) “Поставщик пользователей”

В дополнение к вашему классу User, вам также нужен “Постащик пользователей”: класс, которые помогает с некоторыми вещами, вроде перезагрузки данных Пользоателя из сессии и некоторыми другими функциями, вроде запомнить меня и имперсонации.

К счастью, комагда make:user уже сконфигурировала его для вас в вашем файле security.yaml под ключом providers:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    # config/packages/security.yaml
    security:
        # ...
    
        providers:
            # используется для перезагрузки пользователя из сесиии и других функций (например, switch_user)
            app_user_provider:
                entity:
                    class: App\Entity\User
                    property: email
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- 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
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <!-- используется для перезагрузки пользователя из сесиии и других функций (например, switch_user) -->
            <provider name="app_user_provider">
                <entity class="App\Entity\User" property="email"/>
            </provider>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    // config/packages/security.php
    use App\Entity\User;
    
    $container->loadFromExtension('security', [
        // ...
    
        'providers' => [
            // используется для перезагрузки пользователя из сесиии и других функций (например, switch_user)
            'app_user_provider' => [
                'entity' => [
                    'class' => User::class,
                    'property' => 'email',
                ],
            ],
        ],
    ]);
    

Если ваш класс User является сущностью, вам не нужно больше ничего делать. Но если ваш класс не сущность, то make:user также сгенерирует класс UserProvider, которые вам нужно закончить. Узнайте больше о поставщиках пользователей здесь: Поставщики пользователей.

2c) Хеширование паролей

Не все приложения имеют “пользователей”, которым нужны пароли. Если ваши пользователи имеют пароли, вы можете контролировать, как эти пароли хешируются в security.yaml. Команда make:user будет предварительно конфигурировать это для вас:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    # config/packages/security.yaml
    security:
        # ...
    
        password_hashers:
            # используйте имя вашего класса пользователя здесь
            App\Entity\User:
                # используйте нативный хешировщик паролей, который автоматически выбирает лучший
                # возможный алгоритм хеширования (начиная с Symfony 5.3 - это "bcrypt")
                algorithm: auto
    
  • 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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <security:password-hasher class="App\Entity\User"
                algorithm="auto"
                cost="12"/>
    
            <!-- ... -->
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // config/packages/security.php
    use App\Entity\User;
    
    $container->loadFromExtension('security', [
        // ...
    
        'password_hashers' => [
            User::class => [
                'algorithm' => 'auto',
                'cost' => 12,
            ]
        ],
    
        // ...
    ]);
    

New in version 5.3: Опция password_hashers была представлена в Symfony 5.3. В предыдущих версиях она называлась encoders.

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

К примеру, используя DoctrineFixturesBundle, вы можете создать фиктивных пользователей базы данных:

1
2
3
4
$ php bin/console make:fixtures

Имя класса тестов для создания (например, AppFixtures):
> UserFixtures

Используйте этот сервис для хеширования паролей:

 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
  // src/DataFixtures/UserFixtures.php

+ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  // ...

  class UserFixtures extends Fixture
  {
+     private $passwordHasher;

+     public function __construct(UserPasswordHasherInterface $passwordHasher)
+     {
+         $this->passwordHasher = $passwordHasher;
+     }

      public function load(ObjectManager $manager)
      {
          $user = new User();
          // ...

+         $user->setPassword($this->passwordHasher->hashPassword(
+             $user,
+             'the_new_password'
+         ));

          // ...
      }
  }

Вы можете вручную хешировать пароль, выполнив:

1
$ php bin/console security:hash-password

3a) Аутентификация и файерволлы

New in version 5.1: Опция lazy: true была представлена в Symfony 5.1. До версии 5.1, она включалась с использованием anonymous: lazy

Система безопасности конфигурируется в config/packages/security.yaml. Наиболее важным разделом является firewalls:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # config/packages/security.yaml
    security:
        firewalls:
            dev:
                pattern: ^/(_(profiler|wdt)|css|images|js)/
                security: false
            main:
                anonymous: true
                lazy: true
    
  • 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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <config>
            <firewall name="dev"
                pattern="^/(_(profiler|wdt)|css|images|js)/"
                security="false"/>
    
            <firewall name="main"
                anonymous="true"
                lazy="true"/>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // config/packages/security.php
    $container->loadFromExtension('security', [
        'firewalls' => [
            'dev' => [
                'pattern' => '^/(_(profiler|wdt)|css|images|js)/',
                'security' => false,
            ],
            'main' => [
                'anonymous' => true,
                'lazy' => true,
            ],
        ],
    ]);
    

“Файерволл” - это ваша система аутентификации: конфигурация под ней определяет то, как ваши пользователи будут аутентифицироваться (например, форма входа в систему, API-токен, и др.).

В каждом запросе активен только один файерволл: Symfony использует ключ pattern, чтобы найти первое совпадение (вы также можете сопоставлять с хостом или другими вещами). Файерволл dev на самом деле фальшивый: он гарантирует, что вы случайно не заблокируете инструменты разработки Symfony - которые живут по URL вроде /_profiler и /_wdt.

Все настоящие URL обрабатываются главным файерволлом main (отсутствие ключа pattern означает, что он совпадает со всеми URL). Файерволл может иметь множество режимов аутентификации, другими словами, множество способов задавать вопрос “Кто вы?”. Часто, пользователь неизвествен (т.е. не выполнил вход в систему), когда пытается посетить ваш сайт. Режим anonymous, если он включен, используется для таких запросов.

На самом деле, если вы перейдете на домашнюю страницу праямо сейчас, вы будете иметь доступ и вы увидите, что вы “аутентифицированы” как anon.. Файерволл убедился, что не знает вашей личности и поэтому вы анонимны:

_images/anonymous_wdt.png

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

Вы позже узнаете, как отказывать в доступе к определенным URL, контроллерам или частям шаблонов.

Tip

Режим анонимности lazy предотвращает сессию от запуска, если нет необходимости для авторизации (т.е. ясной проверки привелегии пользователя). Это важно для того, чтобы запросы оставались кешируемыми (см; HTTP Cache).

Note

Если вы не видите панель инструментов, установите профилировщик с:

1
$ composer require --dev symfony/profiler-pack

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

3b) Аутентификация ааших пользователей

Аутентификация в Symfony может ощущаться немного “волшебной” по началу. Это потому, что вместо создания маршрута и контроллера для обработки входа в систему, вы будете активировать поставщика аутентификации: некоторый код, который автоматически выполняется до вызова вашего контроллера.

Symfony имеет несколько встроенных поставщиков аутентификации. Если ваш пример использование совпадает точно с одним из этих, отлично! Но, в большинстве случаев, включая форму входа в систему, мы рекомендуем создание аутентификатора Guard - класса, позволяющего вам контролировать каждую часть процесса аутентификации (см. следующий раздел).

Tip

Если ваше приложение позволяет вашим пользователям входить через сторонний сервис вроде Google, Facebook или Twitter (социальный вход), посмотрите пакет сообщества HWIOAuthBundle .

Аутентификаторы Guard

Аутентификатор Guard - это класс, который дает вам полный контроль над вашим процессом аутентификации. Существует множество способов создания аутентификтора; вот наиболее распространенные:

Ограничение попыток входа

New in version 5.2: Регулирование входа было представлено в Symfony 5.2.

Symfony предоставляет базовую защиту от `атак входа полным перебором`_, если вы используете экспериментальные аутентификаторы. Вы должны включить это, используя настройку login_throttling:

  • YAML
     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
    # config/packages/security.yaml
    security:
        enable_authenticator_manager: true
    
        firewalls:
            # ...
    
            main:
                # ...
    
                # по умолчанию, функция позволяет 5 попыток входа в минуту
                login_throttling: null
    
                # сконфигурируйте максимум попыток входа (в минуту)
                login_throttling:
                    max_attempts: 3
    
                # сконфигурируйте максимум попыток входа в течение заданного периода времени
                login_throttling:
                    max_attempts: 3
                    interval: '15 minutes'
    
                # используйте пользовательский ограничитель скорости обработки запросов через его сервис ID
                login_throttling:
                    limiter: app.my_login_rate_limiter
    
  • 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
    26
    27
    28
    <!-- 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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <config enable-authenticator-manager="true">
            <!-- ... -->
    
            <firewall name="main">
                <!-- по умолчанию, функция позволяет 5 попыток входа в минуту -->
                <login-throttling/>
    
                <!-- сконфигурируйте максимум попыток входа (в минуту) -->
                <login-throttling max-attempts="3"/>
    
                <!-- сконфигурируйте максимум попыток входа в течение заданного периода времени -->
                <login-throttling max-attempts="3" interval="15 minutes"/>
    
                <!-- используйте пользовательский ограничитель скорости обработки запросов через его сервис ID -->
                <login-throttling limiter="app.my_login_rate_limiter"/>
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // config/packages/security.php
    $container->loadFromExtension('security', [
        'enable_authenticator_manager' => true,
    
        'firewalls' => [
            // ...
    
            'main' => [
                // по умолчанию, функция позволяет 5 попыток входа в минуту
                'login_throttling' => null,
    
                // сконфигурируйте максимум попыток входа (в минуту)
                'login_throttling' => [
                    'max_attempts' => 3,
                ],
    
                // сконфигурируйте максимум попыток входа в течение заданного периода времени
                'login_throttling' => [
                    'max_attempts' => 3,
                    'interval' => '15 minutes',
                ],
            ],
        ],
    ]);
    

New in version 5.3: Опция login_throttling.interval была представлена в Symfony 5.3.

По умолчанию, попытки входа ограничены в max_attempts (по умолчанию: 5) неудачных попыток для IP address + username и 5 * max_attempts неудачных попыток для IP address. Второе ограничение защищает от обхода первого ограничения нападающими, использующих несколько имен пользователей, без неудобств для обычных пользователей в больших сетях (таких как офисы).

Tip

Ограничение неудачных попыток входа - единственная базовая защита от атак входа полным перебором. Рекомендации `атак входа полным перебором OWASP`_ упоминают несколько других защит, которые вам стоит рассмотреть в зависимости от необходимого уровня защиты.

Если вам нужен более сложный алогритм ограничения, создайте класс, реализующий RequestRateLimiterInterface (или используйте DefaultLoginRateLimiter) и установите опцию limiter как его сервис ID:

  • YAML
     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
    # config/packages/security.yaml
    framework:
        rate_limiter:
            # определите 2 ограничителя обработки запросов (один для username+IP, второй для IP)
            username_ip_login:
                policy: token_bucket
                limit: 5
                rate: { interval: '5 minutes' }
    
            ip_login:
                policy: sliding_window
                limit: 50
                interval: '15 minutes'
    
    services:
        # наш пользовательский ограничитель обработки запросов входа
        app.login_rate_limiter:
            class: Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter
            arguments:
                # globalFactory - ограничитель для IP
                $globalFactory: '@limiter.ip_login'
                # localFactory - ограничитель для username+IP
                $localFactory: '@limiter.username_ip_login'
    
    security:
        firewalls:
            main:
                # используйте пользовательский ограничитель обработки запросов через его сервис ID
                login_throttling:
                    limiter: app.login_rate_limiter
    
  • 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
    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
    <!-- 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:framework="http://symfony.com/schema/dic/symfony"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            https://symfony.com/schema/dic/symfony/symfony-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <framework:config>
            <framework:rate-limiter>
                <!-- определите 2 ограничителя обработки запросов (один для username+IP, второй для IP) -->
                <framework:limiter name="username_ip_login"
                    policy="token_bucket"
                    limit="5"
                >
                    <framework:rate interval="5 minutes"/>
                </framework:limiter>
    
                <framework:limiter name="ip_login"
                    policy="sliding_window"
                    limit="50"
                    interval="15 minutes"
                />
            </framework:rate-limiter>
        </framework:config>
    
        <srv:services>
            <!-- наш пользовательский ограничитель обработки запросов входа -->
            <srv:service id="app.login_rate_limiter"
                class="Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter"
            >
                <!-- 1ый аргумент - ограничитель для IP -->
                <srv:argument type="service" id="limiter.ip_login"/>
                <!-- 2ой аргумент - ограничитель для username+IP -->
                <srv:argument type="service" id="limiter.username_ip_login"/>
            </srv:service>
        </srv:services>
    
        <config>
            <firewall name="main">
                <!-- используйте пользовательский ограничитель обработки запросов через его сервис ID -->
                <login-throttling limiter="app.login_rate_limiter"/>
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     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
    // config/packages/security.php
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use Symfony\Component\DependencyInjection\Reference;
    use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter;
    use Symfony\Config\FrameworkConfig;
    use Symfony\Config\SecurityConfig;
    
    return static function (ContainerBuilder $container, FrameworkConfig $framework, SecurityConfig $security) {
        $framework->rateLimiter()
            ->limiter('username_ip_login')
                ->policy('token_bucket')
                ->limit(5)
                ->rate()
                    ->interval('5 minutes')
        ;
    
        $framework->rateLimiter()
            ->limiter('ip_login')
                ->policy('sliding_window')
                ->limit(50)
                ->interval('15 minutes')
        ;
    
        $container->register('app.login_rate_limiter', DefaultLoginRateLimiter::class)
            ->setArguments([
                // 1ый аргумент - ограничитель для IP
                new Reference('limiter.ip_login'),
                // 2ой аргумент - ограничитель для username+IP
                new Reference('limiter.username_ip_login'),
            ]);
    
        $security->firewall('main')
            ->loginThrottling()
                ->limiter('app.login_rate_limiter')
        ;
    };
    

4) Отказ в доступе, роли и другая авторизация

Теперь пользователи могут входить в ваше приложение, используя http_basic или какой-то другой метод. Отлично! Теперь вам надо узнать, как отказать в доступе и работать с объектом User. Это называется авторизация, и ее задача - решить, имеет ли пользователь доступ к какому-то ресурсу (URL, объекту модели, методу вызова и т.д.).

Процесс авторизации имеет две стороны:

  1. Пользователь получает конкретный набор ролей, когда входит в систему (например, ROLE_ADMIN);
  2. Вы добавляете код, чтобы ресурс (например, URL или контроллер) требовал специальный «атрибут» (чаще всего роль вроде ROLE_ADMIN) для получения доступа.

Роли

Когда пользователь выполняет вход, Symfony вызывает метод getRoles() в вашем объекте User, чтобы определить, какие роли имеет этот пользователь. В классе User, который мы сгенерировали ранее, роли - это массив, хранящийся в базе данных, и каждый пользователь всегда имеет хотя бы одну роль: ROLE_USER:

// src/Entity/User.php

// ...
class User
{
    /**
     * @ORM\Column(type="json")
     */
    private $roles = [];

    // ...
    public function getRoles(): array
    {
        $roles = $this->roles;
        // гарантировать каждому пользователю хотя бы ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }
}

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

  • Каждая роль должна начинаться с ROLE_ (иначе все не будет работать, как должно)
  • Кроме правила выше, роль - это просто строка, и вы можете выдумать, что вам нужно (например, ROLE_PRODUCT_ADMIN).

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

Добавление кода для отказа в доступе

Существует два способа отказать в доступе к чему-либо:

  1. access_control в security.yaml позволяет вам защитить паттерны URL (например, /admin/*). Это легкий, но менее гибкий способ;
  2. в вашем контроллере (или другом коде).

Безопасность URL-паттернов (access_control)

Самым базовым способом защитить часть вашего приложения является защита всего шаблона URL. Вы видели это раньше, когда всему, совпадающему с регулярным выражением ^/admin необходима роль ROLE_ADMIN:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # config/packages/security.yaml
    security:
        # ...
    
        firewalls:
            # ...
            main:
                # ...
    
        access_control:
            # требовать ROLE_ADMIN для /admin*
            - { path: ^/admin, roles: ROLE_ADMIN }
    
            # или требовать ROLE_ADMIN или IS_AUTHENTICATED_FULLY для /admin*
            - { path: '^/admin', roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }
    
            # значение 'path' может быть любым валидным регулярным выражением
            # (будет совпадать с URL вроде /api/post/7298 и /api/comment/528491)
            - { path: ^/api/(post|comment)/\d+$, roles: ROLE_USER }
    
  • 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
    26
    27
    28
    29
    30
    31
    <!-- 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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <firewall name="main">
                <!-- ... -->
            </firewall>
    
            <!-- требовать ROLE_ADMIN для /admin* -->
            <rule path="^/admin" role="ROLE_ADMIN" />
    
            <!-- требовать ROLE_ADMIN или IS_AUTHENTICATED_FULLY для /admin* -->
            <rule path="^/admin">
                <role>ROLE_ADMIN</role>
                <role>IS_AUTHENTICATED_FULLY</role>
            </rule>
    
            <!-- значение 'path' может быть любым валидным регулярным выражением
                 (будет совпадать с URL вроде /api/post/7298 и /api/comment/528491) -->
            <rule path="^/api/(post|comment)/\d+$" role="ROLE_USER"/>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // config/packages/security.php
    $container->loadFromExtension('security', [
        // ...
    
        'firewalls' => [
            // ...
            'main' => [
                // ...
            ],
        ],
        'access_control' => [
            // требовать ROLE_ADMIN для /admin*
            ['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
    
            // требовать ROLE_ADMIN или IS_AUTHENTICATED_FULLY для /admin*
            ['path' => '^/admin', 'roles' => ['ROLE_ADMIN', 'IS_AUTHENTICATED_FULLY']],
    
            // значение 'path' может быть любым валидным регулярным выражением
            // (будет совпадать с URL вроде /api/post/7298 и /api/comment/528491)
            ['path' => '^/api/(post|comment)/\d+$', 'roles' => 'ROLE_USER'],
        ],
    ]);
    

Вы можете определить столько паттернов URL, сколько вам нужно - каждый является регулярным выражением. НО только один будет сопоставлен. Symfony будет рассматривать каждый, начиная с начала списка, и остановится как только найдет первое совпадение.

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    # config/packages/security.yaml
    security:
        # ...
    
        access_control:
            # совпадает с /admin/users/*
            - { path: '^/admin/users', roles: ROLE_SUPER_ADMIN }
    
            # совпадает с /admin/* кроме чего-либо, совпдающего с правилом выше
            - { path: '^/admin', roles: ROLE_ADMIN }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- 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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <rule path="^/admin/users" role="ROLE_SUPER_ADMIN"/>
            <rule path="^/admin" role="ROLE_ADMIN"/>
        </config>
    </srv:container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // config/packages/security.php
    $container->loadFromExtension('security', [
        // ...
    
        'access_control' => [
            ['path' => '^/admin/users', 'roles' => 'ROLE_SUPER_ADMIN'],
            ['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
        ],
    ]);
    

Вставка в начале маршрута символа ^ означает, что только URL начинающиеся с шаблона, будут сопоставлены. Например, путь /admin (без ^), будет совпадать с /admin/foo, но также будет совпадать с такими URL как /foo/admin.

Каждый access_control также может сопоставляться с IP-адресом, именем хоста и HTTP-методами. Он также может быть использован для перенаправления пользователя к https версии паттерна URL. См. How Does the Security access_control Work?.

Безопасность контроллеров и другого кода

Вы можете с легкостью отказать в доступе из контроллера:

// src/Controller/AdminController.php
// ...

public function adminDashboard(): Response
{
    $this->denyAccessUnlessGranted('ROLE_ADMIN');

    // или добавьте необязательное сообщение - увидено разработчиками
    $this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'User tried to access a page without having ROLE_ADMIN');
}

Вот и все! Если доступ не предоставлен, будет вызвано специальное исключение AccessDeniedException. и не будет вызван больше никакой код в вашем контроллере. Затем, случится одна из двух вещей:

  1. Если пользователь еще не выполнил вход, его попросят это сделать (например, перенапрвят на страницу входа в систему).
  2. Если пользователь уже выполнил вход, но не имеет роли ROLE_ADMIN, ему покажут страницу отказа в доступе 403 (которую вы можете настроить).

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

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

+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

+ /**
+  * Требовать ROLE_ADMIN для *каждого* метода контроллера в этом классе.
+  *
+  * @IsGranted("ROLE_ADMIN")
+  */
  class AdminController extends AbstractController
  {
+     /**
+      * Требовать ROLE_ADMIN только для этого метода контроллера.
+      *
+      * @IsGranted("ROLE_ADMIN")
+      */
      public function adminDashboard(): Response
      {
          // ...
      }
  }

Для более подробной информации смотрите `документацию FrameworkExtraBundle`_.

Контроль доступа в шаблонах

Если вы хотите проверить, имеет ли текущий пользователь роль в шаблоне, используйте встроенную функцию помощника is_granted():

1
2
3
{% if is_granted('ROLE_ADMIN') %}
    <a href="...">Delete</a>
{% endif %}

Безопасность других сервисов

См. How to Secure any Service or Method in your Application.

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

Большинство приложений требуют определенных правил доступа. Например, пошльзователь должен быть способен редактировать только свои комментарии в блоге. Избиратели позволяют вам писать любую бизнес-логику, которая вам необходима для определения доступа. Использование этих избирателей похоже на проврку доступа на основании ролей, реализовунную в предыдущих главах. Прочтите How to Use Voters to Check User Permissions чтобы узнать, как реализовать собственного избирателя.

Проверка выполнения входа пользователем (IS_ATHENTICATED_FULLY)

Если вы только хотите проверить, выполнил ли пользователь вход в систему (вам не важны роли), у вас есть две опции. Для начала, если вы дали каждому пользователю ROLE_USER, вы можете проверить эту роль. В других случаях, вы можете использовать специальный “атрибут” вместо роли:

// ...

public function helloAction($name)
{
    if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
        throw $this->createAccessDeniedException();
    }

    // ...
}

Вы можете использовать IS_AUTHENTICATED_FULLY везде, где используются роли: вроде access_control или в Twig.

IS_AUTHENTICATED_FULLY не роль, но ведет себя похоже на нее, и каждый пользователь, который успешно выполнил вход, будет иметь его. На самом деле, есть три таких специальных атрибута:

  • IS_AUTHENTICATED_REMEMBERED: Все пользователи, выполнившие вход, имеют это, даже если они в системе благодаря куки-файлу запомнить меня functionality. Даже если вы не используете функционал «запомнить меня», вы можете использовать это, чтобы проверить, вошел ли пользователь.

  • IS_AUTHENTICATED_FULLY: Это похоже на IS_AUTHENTICATED_REMEMBERED, но сильнее. Пользователи, которые вошли только благодаря куки-файлу «запомнить меня», будут иметь IS_AUTHENTICATED_REMEMBERED, но не будут иметь IS_AUTHENTICATED_FULLY.

  • IS_AUTHENTICATED_ANONYMOUSLY: Все пользователи (даже анонимные) имеют это - это удобно при использовании технологии белых списков URL для гарантии доступа - некоторые детали можно узнать в Как работает безопасность access_control.

  • IS_ANONYMOUS: Только анонимные пользователи сопоставляются этим атрибутом.

  • IS_REMEMBERED: Только аутентифицированные пользователи, использующие функционал “запомнить меня”, (т.e. cookie remember-me ).

  • IS_IMPERSONATOR: Когда текущий пользователь имперсонирует другого пользователя в этой

    сессии, этот атрибут будет совпадать.

New in version 5.1: Атрибуты IS_ANONYMOUS, IS_REMEMBERED и IS_IMPERSONATOR были представлены в Symfony 5.1.

5a) Извлечение объекта Пользователя

После аутентификации, к объекту User текущего пользователя можно получить доступ через сокращение getUser():

public function index(): Response
{
    // обычно вы сначала захотите гарантировать, что пользователь аутентифицирован
    $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

    // возвращает ваш объект User, или null, если пользователь не аутентифицирован
    // использует встроенную документацию, чтобы сообщить вашему редкатору ваш точный класс User
    /** @var \App\Entity\User $user */
    $user = $this->getUser();

    // Вызвать те методы, которые вы добавили к вашему классу User
    // Например, если вы добавили метод getFirstName(), вы можете его использовать.
    return new Response('Well hi there '.$user->getFirstName());
}

5b) Извлечение Пользователя из сервиса

Если вам нужно получить пользователя, выполнившего вход, из сервиса, используйте сервис Security:

// src/Service/ExampleService.php
// ...

use Symfony\Component\Security\Core\Security;

class ExampleService
{
    private $security;

    public function __construct(Security $security)
    {
        // Избегайте вызова getUser() в конструкторе: аутентификация может быть
        // еще не завершена. Вместо этого, сохраните весь объект Security.
        $this->security = $security;
    }

    public function someMethod()
    {
        // возвращает объект User или null, если аутентификация не пройдена
        $user = $this->security->getUser();

        // ...
    }
}

Извлечение Пользователя в шаблоне

В шаблоне Twig объект пользователя доступен через переменную app.user благодаря глобальной переменной приложения Twig:

1
2
3
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
    <p>Email: {{ app.user.email }}</p>
{% endif %}

Выход из системы

Для включения выхода из системы, активируйте параметр кофнигурации logout под вашим файерволлом:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    # config/packages/security.yaml
    security:
        # ...
    
        firewalls:
            main:
                # ...
                logout:
                    path:   app_logout
    
                    # куда перенаправить после выхода
                    # target: app_any_route
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- 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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <firewall name="secured_area">
                <!-- ... -->
                <logout path="app_logout"/>
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // config/packages/security.php
    $container->loadFromExtension('security', [
        // ...
    
        'firewalls' => [
            'secured_area' => [
                // ...
                'logout' => ['path' => 'app_logout'],
            ],
        ],
    ]);
    

Далее, вам понадобится создать маршрут для этого URL (но не контроллер):

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/Controller/SecurityController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class SecurityController extends AbstractController
    {
        /**
         * @Route("/logout", name="app_logout", methods={"GET"})
         */
        public function logout(): void
        {
            // контроллер может быть пустым: он никогда не будет выполнен!
            throw new \Exception('Don\'t forget to activate logout in security.yaml');
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/Controller/SecurityController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class SecurityController extends AbstractController
    {
        #[Route('/logout', name: 'app_logout', methods: ['GET'])]
        public function logout()
        {
            // контроллер может быть пустым: он никогда не будет выполнен!
            throw new \Exception('Don\'t forget to activate logout in security.yaml');
        }
    }
    
  • YAML
    1
    2
    3
    4
    # config/routes.yaml
    app_logout:
        path: /logout
        methods: GET
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- config/routes.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
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="app_logout" path="/logout" methods="GET"/>
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // config/routes.php
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('app_logout', '/logout')
            ->methods(['GET'])
        ;
    };
    

И это все! Отправив пользователя по маршруту app_logout (т.е. на /logout), Symfony разаутентифицирует текущего пользователя и перенаправит его.

Настройка выхода

New in version 5.1: LogoutEvent был представлен в Symfony 5.1. До этой версии, вам нужно было использовать обработчик удачного выхода из системы для настройки выхода.

В некоторых случаях вам понадобится выполнить дополнительную логику во время выхода из системы (например, инвалидировать какие-то токены) или вы захотите настроить то, что происходит после выхода. Во время выхода, запускается LogoutEvent. Зарегистрируйте слушателя или подписчика события, чтобы выполнять пользовательскую логику. Следующая информация доступна в клссе события:

getToken()
Возвращает токен безопасности каждой сессии, из которой сейчас будет выполнен выход.
getRequest()
Возвращает текущий запрос.
getResponse()
Возврващает запрос, если он уже установлен пользовательским слушателем. Используйте setResponse(), чтобы сконфигурировать пользовательский ответ выхода.

Tip

Каждый файерволл Безопасности имеет собственный диспетчер событий (security.event_dispatcher.FIREWALLNAME). Событие выхода запускатеся как в глоабальном диспетчере, так и в диспетчере файерволла. Вы можете зарегистрироваться в диспетчере файерволла, если вы хотите, чтобы ваш слушатель выполнялся только для конкретного файерволла. Например, если у вас есть файерволл api и main, используйте эту конфигурацию для регистрации только в событии выхода в файерволле main:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # config/services.yaml
    services:
        # ...
    
        App\EventListener\CustomLogoutSubscriber:
            tags:
                - name: kernel.event_subscriber
                  dispatcher: security.event_dispatcher.main
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- ... -->
    
            <service id="App\EventListener\CustomLogoutSubscriber">
                <tag name="kernel.event_subscriber"
                     dispacher="security.event_dispatcher.main"
                 />
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use App\EventListener\CustomLogoutListener;
    use App\EventListener\CustomLogoutSubscriber;
    use Symfony\Component\Security\Http\Event\LogoutEvent;
    
    return function(ContainerConfigurator $configurator) {
        $services = $configurator->services();
    
        $services->set(CustomLogoutSubscriber::class)
            ->tag('kernel.event_subscriber', [
                'dispatcher' => 'security.event_dispatcher.main',
            ]);
    };
    

Иерархические роли

Вместо того, чтобы ассоциировать пользователей со многими ролями, вы можете определить правила наследования, создав иерархию ролей:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/packages/security.yaml
    security:
        # ...
    
        role_hierarchy:
            ROLE_ADMIN:       ROLE_USER
            ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- 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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/security
            https://symfony.com/schema/dic/security/security-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <role id="ROLE_ADMIN">ROLE_USER</role>
            <role id="ROLE_SUPER_ADMIN">ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH</role>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // config/packages/security.php
    $container->loadFromExtension('security', [
        // ...
    
        'role_hierarchy' => [
            'ROLE_ADMIN'       => 'ROLE_USER',
            'ROLE_SUPER_ADMIN' => [
                'ROLE_ADMIN',
                'ROLE_ALLOWED_TO_SWITCH',
            ],
        ],
    ]);
    

Пользователи с ролью ROLE_ADMIN также будут иметь роль ROLE_USER. А пользователи с ROLE_SUPER_ADMIN, будут автоматически иметь ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH и ROLE_USER (унаследованную от ROLE_ADMIN).

Для того. чтобы иерархия ролей работала, не пытайтесь вызывать $user->getRoles() вручную. К примеру, в контроллере, расширяющемся из базового контроллера:

// ПЛОХО - $user->getRoles() не будет знать об иерархии ролей
$hasAccess = in_array('ROLE_ADMIN', $user->getRoles());

// ХОРОШО - использование нормальных методов безопасности
$hasAccess = $this->isGranted('ROLE_ADMIN');
$this->denyAccessUnlessGranted('ROLE_ADMIN');

Note

Значения role_hierarchy статичны - вы не можете, к примеру, хранить иерархию ролей в базе данных. Если вам это нужно. создайте пользовательский role hierarchy in a database. If you need that, create a custom избиратель безопасности. который ищет роли пользователей в базе данных.

Часто задаваемые вопросы

Может ли у меня быть несколько файерволлов?
Да! Но обычно это не нужно. Каждый файерволл - это как отдельная система безопасности. И поэтому, разве что у вас нет очень разных потребностей аутентификации, одного файерволла обычно дстаточно. С аутентификацией Guard, вы можете создать разнообразные способы разрешений аутентификации (например, форму входа в систему, аутентификацию ключей API и LDAP) - все под одним файерволлом.
Могу ли я использовать одну аунтентификацию между файерволлами?
Да, но только с некоторой конфигурацией. Если вы используете несколько файерволлов, и аутенифицируете в одном, вы не будете автоматически аутентифицированы ни в одном другом. Разные файерволлы - это как разные системы безопасности. Чтобы сделать это, вам нужно ясно указать один Firewall Context для разных файерволлов. Но обычно, для большинства приложений, одного основного файерволла достаточно.
Безопасность похоже не работает на моих страницах ошибок
Так как маршрутизация выполняется до безопасности, страницы 404 не покрываются ни одним файерволлом. Это означает, что вы не можете проверять безопасность или даже получить доступ к объекту пользователя на этих страницах. См. How to Customize Error Pages, чтобы узнать больше деталей.
Похоже, моя аутентификация не работает: ошибок нет, но я никогда не нахожусь в системе
Иногда аутентификация может быть успешной, но после перенаправления, вы немедленно выходите из системы в связи с проблемой загрузки User из сессии. Чтобы увидеть, в этом ли проблема, проверьте ваш файл логов (var/log/dev.log) на предмет сообщения лога:
Не могу обновить токен, так как пользователь изменился
Если вы видите это, есть две возможных причины. Первая может заключаться в проблеме загрзуки вашего Пользователя из сессии. См. Understanding how Users are Refreshed from the Session. Вторая, если определенная информация пользователя была изменена в базе данных со времени последнего обновления страницы, Symfony специально выполнит выход пользователя из сестемы по соображениям безопасности.

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