Безопасность
Дата обновления перевода 2024-08-01
Безопасность
Symfony предоставляет множество инструментов для безопасности вашего приложения. Некоторые инструменты безопасности, связанные с HTTP, вроде куки безопасных сессий и CSRF-защита предоставляются по умолчанию. SecurityBundle, о котором вы узнаете в этом руководстве, предоставляет все необходимые функции аутентификации и авторизации для безопасности вашего приложения.
Чтобы начать, установите SecurityBundle:
1
$ composer require symfony/security-bundle
Если у вас установлен Symfony Flex , он также создаст
для вас файл конфигурации security.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
# config/packages/security.yaml
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#c-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
users_in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_in_memory
# активируйте разные способы аутентификации
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Простой способ контролировать доступ к большим разделам вашего сайта
# Примечание: Будет использован только *первый* совпадающий контроль доступа
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
Это много конфигурации! В следуюших разделах обсуждаются три основных элемента:
- Пользователь (
providers
) - Любому защищенному разделу вашего приложения нужен некоторый концепт пользователя. Поставщик пользователей загружает пользователей из любого хранилища (например, базы данных), основываясь на "идеантификаторе пользователя" (например, адресе электронной почты пользователя);
- Брандмауэр & Аутентификация пользователей (
firewalls
) - Брандмауэр - это основа безопасности вашего приложения. Каждый запрос в рамках брандмауэра проверяется на необходимость аутентификации пользователя. Брандмауэр также заботится об аутентификации этого пользователя (например, используя форму входа);
- Контроль доступа (Авторизация) (
access_control
) - Используя контроля доступа и проверщика авторизации, вы контролируете необходимые разрешения для выполнения конкретного действия или посещения конкретного URL.
Пользователь
Разрешения в Symfony всегда связаны с объектов пользователя. Если вам нужно защитить ваше приложение (или его части), вам нужно создать класс пользователя. Этот класс реализует UserInterface. Он зачастую является сущностью Doctrine, но вы также можете использовать соответствующий класс пользователя Безопасности.
Самый простой способ сгенерировать класс пользователя - используя команду
make:user
из MakerBundle:
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
Вы хотите хранить данные пользователя в БД (через Doctrine)? (да/нет) [yes]:
> yes
Введите имя свойства, которое будет уникальным "отображаемым" именем пользователя (например, адрес почты, имя пользователя, 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
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
// src/Entity/User.php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'string', length: 180, unique: true)]
private ?string $email;
#[ORM\Column(type: 'json')]
private array $roles = [];
#[ORM\Column(type: 'string')]
private string $password;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* Публичное представление пользователя (например, имя пользователя, адрес почты и т.д.)
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// гарантировать, что у каждого пользователя есть хотя бы ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function eraseCredentials(): void
{
// Если вы храните любые временные чувствительные данные пользователя, очистите их здесь
// $this->plainPassword = null;
}
}
Tip
Начиная с MakerBundle: v1.57.0 - Вы можете передавать --with-uuid
или
--with-ulid
в make:user
. Используя Компонент Uid от Symfony,
создается сущность User
с типом id
в виде :ref:a05daa4c14c5bacd568cb31237544450398719d3int``.
Если ваш пользователь является сущностью Doctrine, как в примере выше, не забудьте создать таблицы, создав и запустив миграцию :
1 2
$ php bin/console make:migration
$ php bin/console doctrine:migrations:migrate
Tip
Начиная с MakerBundle: v1.56.0 - Передача --formatted
в make:migration
генерирует красивый и аккуратный файл миграции.
Загрузка пользователя: Поставщик пользователей
Кроме создания сущности, команда make:user
также добавляет конфигурацию
для поставщика пользователей в вашу конфигурацию безопасности:
1 2 3 4 5 6 7 8 9
# config/packages/security.yaml
security:
# ...
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
Этот поставщик пользователей знает, как (пере)загружать пользователей из хранилища
(например, БД), основываясь на "идентификаторе пользователя" (например, адресе почты
или имени пользователя). Конфигурация выше использует Doctrine для загрузки сущности
User
, используя свойство email
как "user identifier".
Поставщики пользователей используются в нескольких местах во время жизненного цикла безопасности:
- Загрузка пользователя, основываясь на идентификаторе
- Во время входа (или любой другой аутентификации), поставщик загружает пользователя, основываясь на его идентификаторе пользователя. Некоторые другие функции, вроде имитации пользователя и Запомнить меня также используют это.
- Перезагрузка пользователя из сессии
-
В начале каждого запроса, пользователь загружается из сессии (кроме
случаев, когда ваш брандмауэр
stateless
). Поставщик "обновляет" пользователя (например, снова делает запрос к БД для свежих данных), чтобы убедиться, что вся информация пользователя обновлена (и, если необходимо, пользователь деаутентифицируется/выводится из системы, если что-то изменилось). См. Поставщики пользователей Безопасности, чтобы узнать больше об этом процессе.
Symfony поставляется с несколькими встроенными поставщиками пользователей:
- Поставщик пользователей сущности
- Загружает пользователей из БД, используя Doctrine;
- Поставщик пользователей LDAP
- Загружает пользователей с LDAP-сервера;
- Поставщик пользователей памяти
- Загружает пользователей из файла конфигурации;
- Цепной постащик пользователей
- Слияет два или более поставщика пользоваталей в нового поставщика пользователей. Поскольку каждый брандмауэр имеет ровно одного поставщика пользователей, вы можете использовать это для объединения нескольких поставщиков в цепочку.
Встроенные поставщики пользователей покрывают наиболее распространенные задачи приложений, но вы также можете создать собственного пользовательского поставщика пользователей .
Note
Иногда вам может понадобиться внедрить поставщика пользователей в другой
класс (например, в ваш пользовательский аутентификатор). Все поставщики
пользователей следуют этому паттерну для своих ID сервисов:
security.user.provider.concrete.<your-provider-name>
(где <your-provider-name>
- ключ конфигурации, например, app_user_provider
).
Если у вас только один поставщик пользователей, вы можете автомонтировать его,
используя подсказку UserProviderInterface.
Регистрация пользователя: Хеширование паролей
Множество приложений требуют входа в систему с помощью пароля. Для таких приложений SecurityBundle предоставляет хеширование паролей и верификацию функциональности.
Для начала, убедитесь, что ваш класс User реализует PasswordAuthenticatedUserInterface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/Entity/User.php
// ...
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ...
/**
* @return строку с хешированным паролем для этого пользователя
*/
public function getPassword(): string
{
return $this->password;
}
}
Затем, сконфигурируйте, какой хешировщик паролей должен быть использован для этого класса
пользователя. Если ваш файл security.yaml
еще не был предварительно сконфигурирован, то
make:user
должен был сделать это за вас:
1 2 3 4 5 6 7
# config/packages/security.yaml
security:
# ...
password_hashers:
# Использовать нативный хешировщик паролей, который автоматически выбирает и мигрирует лучший
# возможный алгоритм хеширования (начиная с Symfony 5.3 это "bcrypt")
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
Теперь, когда Symfony знает как вы хотите хешировать пароли, вы можете использовать
сервис UserPasswordHasherInterface
, чтобы делать это до сохранения ваших пользователей
в базу данных:
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
// src/Controller/RegistrationController.php
namespace App\Controller;
// ...
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class RegistrationController extends AbstractController
{
public function index(UserPasswordHasherInterface $passwordHasher): Response
{
// ... например, получить данные пользователя из формы регистрации
$user = new User(...);
$plaintextPassword = ...;
// хешировать пароль (основываясь на конфигурации security.yaml для класса $user)
$hashedPassword = $passwordHasher->hashPassword(
$user,
$plaintextPassword
);
$user->setPassword($hashedPassword);
// ...
}
}
Note
Если ваш класс пользователя является сущностью Doctrine и вы хешируете пароли пользователей, класс хранилища Doctrine, связанный с классом пользователя, должен реализовывать PasswordUpgraderInterface.
Tip
Команда-мейкер make:registration-form
может помочь вам настроить контроллер
регистрации и добавить функции вроде верификации адреса электронной почты, используя
SymfonyCastsVerifyEmailBundle.
1 2
$ composer require symfonycasts/verify-email-bundle
$ php bin/console make:registration-form
Вы также можете вручную хешировать пароль, выполнив:
1
$ php bin/console security:hash-password
Прочтите больше обо всех доступных хешироващиках и миграции паролей в Хеширование и верификация паролей.
Брандмауэр
Раздел firewalls
в config/packages/security.yaml
- это самый важный
раздел. "Брандмауэер" - это ваша система аутентификации: брандмауэр определяет,
какие части вашего приложения защищены, и как ваши пользователи будут проходить
аутентификацию (например, форма входа, API-токен и т.д.).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# config/packages/security.yaml
security:
# ...
firewalls:
# порядок, в котором определены брандмауэры, очень важен, так как
# запрос будет обработан первым брандмауэром, чей паттерн совпадет
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# брандмауэр без паттерна должен быть определен последним, потому что он будет совпадать со всеми запросами
main:
lazy: true
# поставщик, который вы установили ранее внутри поставщиков
provider: app_user_provider
# активировать различные способы аутентификации
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
По одному запросу активен только один брандмауэр: Symfony использует ключ
pattern
, чтобы найти первое совпадение (вы также можете
искать совпадения по хостингу или другим вещам).
Брандмауэр dev
на самом деле ненастоящий: он гарантирует, что вы случайно не
заболнируете инструменты разработки Symfony, который живут по URL вроде
/_profiler
и /_wdt
.
Tip
При сопоставлении нескольких маршрутов вместо создания длинного регулярного выражения вы также можете использовать массив более простых регулярных выражений для сопоставления каждого маршрута:
1 2 3 4 5 6 7 8 9 10 11 12
# config/packages/security.yaml
security:
# ...
firewalls:
dev:
pattern:
- ^/_profiler/
- ^/_wdt/
- ^/css/
- ^/images/
- ^/js/
# ...
Эта функция не поддерживается форматом конфигурации XML.
Все настоящие URL обрабатываются брандмауэром main
(отсутствие ключа pattern
означает, что совпадают dct URL). Брандмауэр может иметь множество режимов аутентификации,
другими словами - множество способов задать вопрос "Ты кто?".
Зачастую пользовател неизвестен (т.е. не выполнил вход в систему), когда он впервые попадает на ваш сайт. Если вы посетите свою домашнюю страницу прямо сейчас, у вас будет доступ, и вы увидите, что вы посещаете страницу за брандмауэром в панели инструментов:
Посещение URL под брандмауэром не обязательно требует аутентификации (например, форма входа должна быть доступна или некоторые части вашего приложения являются общедоступными). С другой стороны, все страницы, которые вы хотите, чтобы знали о пользователя в системе, должны находиться под одним и тем же брандмауэром. Поэтому если вы хотите отобразить сообщение "Вы вошли в систему как ... " на каждой странице, все они должны быть включены в один брандмауэр.
Вы узнаете, как ограничить доступ к URL, контроллерам или чему-либо еще в пределах вашего брандмауэра в разделе контроль доступа .
Tip
Анонимный режим lazy
предотвращает сессию от запуска, если нет необходимости
в авторизации (например, ясной проверки привилегий пользователя). Важно оставлять
запросы кешируемыми (см. HTTP-кеширование).
Note
Если вы не видите панель инструментов, установите профилировщик с помощью:
1
$ composer require --dev symfony/profiler-pack
Извлечение конфигурации брандмауэра для запроса
Если вам нужно получить конфигурацию брандмауэра, совпавшего с заданным запросом, используйте сервис Security:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// src/Service/ExampleService.php
// ...
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RequestStack;
class ExampleService
{
public function __construct(
// Избегайте вызова getFirewallConfig() в конструкторе: авторизация может быть еще
// не завершена. Вместо этого, сохраните весь объект Security.
$this->security = $security;
}
public function someMethod(): void
{
$request = $this->requestStack->getCurrentRequest();
$firewallName = $this->security->getFirewallConfig($request)?->getName();
// ...
}
}
Аутентификация пользователей
Во время аутентификации, система пытается найти соответствующего пользователя для посетителя страницы. Традиционно, это делается с помощью формы входа или базового HTTP-диалога в браузере. Однако, SecurityBundle поставляется со множеством других аутентификаторов:
- Форма входа
- Вход JSON
- Базовый HTTP
- Вход по ссылке
- Сертификаты клиентов X.509
- Удаленные пользователи
- Пользовательские аутентификаторы
Tip
Если ваше приложение пропускает пользователей в систему с помощью сторонних сервисов, вроде Google, Facebook или Twitter (социальный вход), посмотрите на общественный пакет HWIOAuthBundle.
Форма входа
Большинство сайтов имеют форму входа, где пользователи проходят аутентификацию, используя идентификатор (например, адрес почты или имя пользователя) и пароль. Этот функционал предоставлен встроенным FormLoginAuthenticator.
Вы можете выполнить следующую команду, чтобы создать все необходимое для добавления формы входа в систему в ваше приложение:
1
$ php bin/console make:security:form-login
Эта команда создаст необходимый контроллер и шаблон, а также обновит конфигурацию безопасности. Если же вы предпочитаете внести эти изменения вручную, выполните следующие шаги.
Для начала, создайте контроллер для формы входа:
1 2 3 4
$ php bin/console make:controller Login
created: src/Controller/LoginController.php
created: templates/login/index.html.twig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/Controller/LoginController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class LoginController extends AbstractController
{
#[Route('/login', name: 'app_login')]
public function index(): Response
{
return $this->render('login/index.html.twig', [
'controller_name' => 'LoginController',
]);
}
}
Затем, подключите FormLoginAuthenticator
, используя настройку form_login
:
1 2 3 4 5 6 7 8 9 10 11
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
form_login:
# "app_login" - это имя ранее созданного маршрута
login_path: app_login
check_path: app_login
Note
login_path
и check_path
поддерживают URL и имена маршрутов (но
не могут иметь обязательных заполнителей - например, /login/{foo}
, где
foo
не имеет значения по умолчанию).
После подключения, система безопасности перенаправляет неаутентифицированных посетителей
на login_path
, если они пробуют попасть в защищенное место (это поведение можно настроить,
используя точки входа аутентификации ).
Отредактируйте контроллер входа, чтобы отобразить форму входа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// ...
+ use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController extends AbstractController
{
#[Route('/login', name: 'app_login')]
- public function index(): Response
+ public function index(AuthenticationUtils $authenticationUtils): Response
{
+ // получить ошибку входа, если она есть
+ $error = $authenticationUtils->getLastAuthenticationError();
+
+ // последнее имя пользователя, введенное пользователем
+ $lastUsername = $authenticationUtils->getLastUsername();
+
return $this->render('login/index.html.twig', [
- 'controller_name' => 'LoginController',
+ 'last_username' => $lastUsername,
+ 'error' => $error,
]);
}
}
Не позволяйте этому контроллеру запутать вас. Его работа только отображать форму:
аутентификатор form_login
позаботится об отправке формы автоматически. Если
пользовател отправляет невалидный адрес почты или пароль, этот аутентификатор сохранит
ошибку и перенаправит обратно к этому контроллеру, где мы прочтем ошибку (используя
AuthenticationUtils
), чтобы она могла быть отражена пользователю.
Наконец, создайте или обновите шаблон:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
{# templates/login/index.html.twig #}
{% extends 'base.html.twig' %}
{# ... #}
{% block body %}
{% if error %}
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<form action="{{ path('login') }}" method="post">
<label for="username">Email:</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>
{% endblock %}
Caution
Переменная error
, переданная шаблону - экземпляр
AuthenticationException.
Она может содержать чувствительную информацию об ошибке аутентификации. Никогда
не используйте error.message
: вмсто этого используйте свойство messageKey
,
как показано в этом примере. Это сообщение всегда безопасно для отображения.
Форма может выглядеть как угодно, но обычно она следует некоторым соглашениям:
- Элемент
<form>
отправляетPOST
маршрутуlogin
, так как вы сконфигурировали это какcheck_path
под ключомform_login
вsecurity.yaml
; - Поле имя пользователя (или любого "идентификатора" пользователя, вроде почты) имеет
имя
_username
, а поле пароля -_password
.
Tip
На самом деле, все это можно сконфигрурировать под ключом form_login
. См.
, чтобы узнать больше.
Danger
Эта форма входа на данный момент не защищена от CSRF-атак. Прочтите , чтобы узнать, как защитить вашу форму входа.
Вот и все! При отправке формы, система безопасности автоматически читает
_username
и параметр POST _password
, загружает пользователя из поставщика
пользователей, проверяет параметры доступа пользователя и либо аутентифицирует его,
либо отправляет обратно в форму входа, где можно отобразить ошибку.
Подведем итог всего процесса:
- Пользователь пробует получить доступ к защищенному ресурсу (например,
/admin
); - Брандмауэр инициирует процесс аутентификации, перенаправляя пользователя к форме входа (
/login
); - Страница
/login
отображает форму входа по маршруту и контроллеру, созданным в этом примере; - Пользователь отправляет форму входа
/login
; - Система безопасности (т.e. аутентификатор
form_login
) перехватывает запрос, проверяет параметры доступа, отправленные пользователем, аутентифицирует пользователя, если они правильные, и отправляет пользователя обратно в форму входа - если нет.
See also
Вы можете настроить ответы успешной и неуспешной попытки входа. См. Как настроить ответы аутентификатора формы входа.
CSRF-защита в формах входа
CSRF-атаки входа можно предотвратить, используя ту же технику добавления спрятанных CSRF-токенов в формы входа. Компонент Безопасность уже предоставляет CSRF-защиту, но вам нужно сконфигурировать некоторые опции перед ее использованием.
Для начала, вам нужно подключить CSRF в форме входа:
1 2 3 4 5 6 7 8 9 10
# config/packages/security.yaml
security:
# ...
firewalls:
secured_area:
# ...
form_login:
# ...
enable_csrf: true
Затем, используйте функцию csrf_token()
в шаблоне Twig, чтобы сгенрировать
CSRF-токен и сохранить его в качестве скрытого поля формы. По умолчанию, HTML-поле
должно называться _csrf_token
, а строка, используемая для генерирования значения,
должна быть authenticate
:
1 2 3 4 5 6 7 8 9 10
{# templates/security/login.html.twig #}
{# ... #}
<form action="{{ path('login') }}" method="post">
{# ... the login fields #}
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<button type="submit">login</button>
</form>
После этого вы защитили вашу форму входа от CSRF-атак.
Tip
Вы можете изменить имя поля, установив csrf_parameter
и изменить ID
токена, установив csrf_token_id
в вашей конфигурации. См.
, чтобы узнать больше.
Вход JSON
Некоторые приложения предоставляют API, защищенный с помощью токенов. Такие приложения могут использовать конечную точку, предоставляющую эти токены, основываясь на имени пользователя (или почте) и пароле. Аутентификатор входа JSON помогает вам фнукционально создвать это.
Включите аутентификтор, используя настройку json_login
:
1 2 3 4 5 6 7 8 9 10
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
json_login:
# api_login - это маршрут, который мы создадим ниже
check_path: api_login
Note
check_path
поддерживает URL и имена маршрутов (но не может иметь
обязательных заполнителей - например, /login/{foo}
, где foo
не
имеет значения по умолчанию).
Аутентификатор запускается, когда клиент запрашивает check_path
. Для начала,
создайте контроллер для этого пути:
1 2 3
$ php bin/console make:controller --no-template ApiLogin
created: src/Controller/ApiLoginController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Controller/ApiLoginController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ApiLoginController extends AbstractController
{
#[Route('/api/login', name: 'api_login')]
public function index(): Response
{
return $this->json([
'message' => 'Welcome to your new controller!',
'path' => 'src/Controller/ApiLoginController.php',
]);
}
}
Этот контроллер входа будет вызван после того, как аутентификатор успешно аутентифицирует пользователя. Вы можете получить аутентифицированного пользователя, сгенерировать токен (или то, что вам надо вернуть) и вернуть JSON-ответ:
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
// ...
+ use App\Entity\User;
+ use Symfony\Component\Security\Http\Attribute\CurrentUser;
class ApiLoginController extends AbstractController
{
- #[Route('/api/login', name: 'api_login')]
+ #[Route('/api/login', name: 'api_login', methods: ['POST'])]
- public function index(): Response
+ public function index(#[CurrentUser] ?User $user): Response
{
+ if (null === $user) {
+ return $this->json([
+ 'message' => 'missing credentials',
+ ], Response::HTTP_UNAUTHORIZED);
+ }
+
+ $token = ...; // как-то создать API-токен для $user
+
return $this->json([
- 'message' => 'Welcome to your new controller!',
- 'path' => 'src/Controller/ApiLoginController.php',
+ 'user' => $user->getUserIdentifier(),
+ 'token' => $token,
]);
}
}
Note
#[CurrentUser]
может быть использован только в аргументах контроллера
для извлечения аутентифицированного пользователя. В сервисах вы будете использовать
getUser().
Вот и все! Подытожим процесс:
Клиент (например, фронтенд) делает запрос POST с заголовком
Content-Type: application/json
к/api/login
сusername
(даже если ваш идентификатор на самом деле - почта) и ключамиpassword
:1 2 3 4
{ "username": "dunglas@example.com", "password": "MyPassword" }
- Система безопасности перехватывает запрос, проверяет отправленные права доступа пользователя и аутентифицирует его. Если права доступа некорректные, возвращается JSON ответ HTTP 401 Неавторизовано, в других случаях запускается ваш контроллер;
Ваш контроллер создает корреный ответ:
1 2 3 4
{ "user": "dunglas@example.com", "token": "45be42..." }
Tip
Формат JSON-запросов может быть сконфигурирован под ключом json_login
.
См. , чтобы узнать больше.
Базовый HTTP
Аутентификация базового HTTP - это стандартизированный фреймворк HTTP-аутентификации. Он запрашивает права доступа (имя пользователя и пароль), используя диалог в браузере и аутентификатор базового HTTP Symfony верифицирует эти права.
Добавьте ключ http_basic
к вашему брандмауэру, чтобы включить аутентификатор
базового HTTP:
1 2 3 4 5 6 7 8 9
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
http_basic:
realm: Secured Area
Вот и все! Каждый раз, когда неаутентифицированный пользователь будет пытаться
посетить защищенную страницу, Symfony будет информировать браузер, что ему нужно
начать базовую HTTP аутентификацию (используя заголовок ответа WWW-Authenticate
).
Затем, аутентификатор верифицирует права доступа и аутенитфицирует пользователя.
Note
Вы не можете использовать выход из системы с базовым аутентификатором HTTP. Даже если вы выйдете из Symfony, ваш браузер "помнит" ваши права доступа и будет отправлять их по каждому запросу.
Вход по ссылке
Вход по ссылке - это безпарольный механизм аутентификации. Пользователь будет получать ссылку с коротким сроком жизни (например, по почте), которая аутентифицирует их на веб-сайте.
Вы можете узнать все об этом аутентификаторе в Как использовать беспарольную аутентификацию ссылки входа в систему.
Токены доступа
Токены доступа часто используются в контексте API. Пользователь получает токен с сервера авторизации, который его аутентифицирует.
Вы можете узнать все об этом аутентификторе в Как использовать аутентификацию токенов доступа.
Сертификаты клиентов X.509
При использовании сертификатов клиентов, ваш веб-сервер делает всю аутентификацию сам. Аутентификатор X.509, предоставленный Symfony, извлекает почту из "уникального имени" (DN) сертификата клиента. Затем, он использует эту почту в качестве идентификатора пользователя в поставщике пользователей.
Для начала, сконфигурируйте ваш веб-сервер, чтобы подключить верификацию сертификатов клиентов, и показать DN сертификатов приложению Symfony:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
server {
# ...
ssl_client_certificate /path/to/my-custom-CA.pem;
# включить верификацию сертификатов клиентов
ssl_verify_client optional;
ssl_verify_depth 1;
location / {
# передать приложению DN как "SSL_CLIENT_S_DN"
fastcgi_param SSL_CLIENT_S_DN $ssl_client_s_dn;
# ...
}
}
Затем, включите аутентификатор X.509, используя x509
в вашем брандмауэре:
1 2 3 4 5 6 7 8 9
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
x509:
provider: your_user_provider
По умолчанию, Symfony извлекает адрес почты из DN двумя способами:
- В начале, она пробует параметр сервера
SSL_CLIENT_S_DN_Email
, который обнажен с помощью Apache; - Если он не установлен (например, при использовании Nginx), она использует
SSL_CLIENT_S_DN
, и сопоставляет значение следующегоemailAddress=
.
Вы можете настроить имена обоих параметров под ключом x509
. См.
the configuration reference ,
чтобы узнать больше.
Удаленные пользователи
Кроме аутентификации сертификатов клиента, существует много других модулей веб-сервера, который предварительно аутентифицируют пользователя (например, kerberos). Удаленный аутентификатор пользователей предоставляет базовую интеграцию для таких сервисов.
Такие модули часто обнажают аутентифицированного пользователя в переменной
окружения REMOTE_USER
. Аутентификатор удаленного пользователя использует
это значение в качестве идентификатора пользователя, чтобы загрузить соответствующего
пользователя.
Включите аутентификацию удаленного пользователя, используя ключ remote_user
:
1 2 3 4 5 6 7
# config/packages/security.yaml
security:
firewalls:
main:
# ...
remote_user:
provider: your_user_provider
Tip
Вы можете настроить имя этой переменной сервера под ключом remote_user
.
См. справочник конфигурации ,
чтобы узнать больше.
Ограничение попыток входа
Symfony предоставляет базовую защиту от атак входа грубой силы благодаря компоненету Rate Limiter. Если вы еще не использовали этот компонент в вашем приложении, установите его, используя эту функцию:
1
$ composer require symfony/rate-limiter
Затем включите эту функцию, используя настройку login_throttling
:
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.yaml
security:
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
Note
Значение опции interval
должно быть числом, за которым следуеют любая
единица меры, принятая относительными форматами дат PHP (например, 3 seconds
,
10 hours
, 1 day
и т.д.)
Внутренне, Symfony использует компонент Rate Limiter, который по умолчанию использует кэш Symfony, чтобы сохранять предыдущие попытки входа. Однако, вы можете реализовать пользовательское хранилище .
Попытки входа ограничиваются в max_attempts
(по умолчанию: 5) неудачных
запросов для IP address + username
и 5 * max_attempts
неудачных запросов
для IP address
. Второе ограничение защищает от того, чтобы хакер использовал
несколько имен пользователя, обходя первое ограничение, не нарушая работу нормальных
пользователей в больших сетях (таких как офисы).
Tip
Ограничение неудачных попыток входа - только базовая защита от атак грубой силы. Руководства Атак грубой силы OWASP упоминают несколько других видов защиты, которые вы должны рассмотреть, в зависимости от необходимого уровня безопасности.
Если вам нужен более сложный алгоритм ограничений, создайте класс, реализующий
RequestRateLimiterInterface
(или используйте
DefaultLoginRateLimiter) и
установите опцию limiter
в ее ID сервиса:
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
Настройка успешного и неудачного поведения аутентификации
Если вы хотите настроить процесс успешной или неудачной аутентификации,
не нужно глобально переписывать соответствующие слушатели. Вместо этого
вы можете установить собственные обработчики успеха и неудачи, реализовав
AuthenticationSuccessHandlerInterface
или AuthenticationFailureHandlerInterface.
Прочитайте как настроить обработчик успеха . для получения дополнительной информации об этом.
Программный вход в систему
Вы можете программно позволитьь пользователю войти в систему, используя метод login()
помощника Security:
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
// src/Controller/SecurityController.php
namespace App\Controller\SecurityController;
use App\Security\Authenticator\ExampleAuthenticator;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
class SecurityController
{
public function someAction(Security $security): Response
{
// заставить пользователя пройти аутентификацию
$user = ...;
// впустить пользователя в систему под текущим брандмауэром
$this->security->login($user);
// елси брандмауэр имеет более одного аутентификатора, вы должны передать его ясно,
// используя имя встроенных аутентификторов...
$this->security->login($user, 'form_login');
// ...или id сервиса пользовательского аутнетификатора
$this->security->login($user, ExampleAuthenticator::class);
// вы можете также войти в систему под другим брандмауэром
$this->security->login($user, 'form_login', 'other_firewall');
// ...и добавить бейджи
$security->login($user, 'form_login', 'other_firewall', [(new RememberMeBadge())->enable()]);
// использовать логику перенаправления, примененную к обычному входу в систему
$redirectResponse = $security->login($user);
return $redirectResponse;
// или использовать пользовательскую логику перенаправления (например, перенаправлять пользователей к странице их аккаунта)
// return new RedirectResponse('...');
}
}
Выход из системы
Чтобы включить выход из системы, активируйте параметр конфигурации logout
под вашим брандмауэром:
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
После этого Symfony лишит аутентификации пользователей, переходящих по сконфигурированному path
,
и перенаправит их к сконфигурированной target
.
Tip
Если вам нужно сослаться на путь выхода из системы, вы можете
использовать имя маршрута _logout_<firewallname>
(например, _logout_main
).
Если в вашем проекте не используется Symfony Flex , убедитесь, что что вы импортировали загрузчик маршрутов выхода из системы в свои маршруты:
1 2 3 4
# config/routes/security.yaml
_symfony_logout:
resource: security.route_loader.logout
type: service
Программный выход из системы
Вы можете вывести пользователя из системы программно, используя метод logout()
помощника Security:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Controller/SecurityController.php
namespace App\Controller\SecurityController;
use Symfony\Bundle\SecurityBundle\Security;
class SecurityController
{
public function someAction(Security $security): Response
{
// вывести пользователя из системы под текущим брандмауэром
$response = $security->logout();
// вы также можете отключить выход из системы csrf
$response = $security->logout(false);
// ... вернуть $response (если установлен) или, к примеру, перенаправить на домашнюю страницу
}
}
Пользователь будет выведен из системы под брандмауэром запроса. Если запрос не находится
за брандмауэром, будет вызвано \LogicException
.
Настройка выхода
В некоторых случаях вам нужно будет выполнить дополнительную логику после выхода из системы (например, инвалидировать некоторые токены), или вам может захотеться настроить то, что происходит после выхода. Во время выхода из системы, запускается LogoutEvent. Зарегистрируйте слушателя или подписчика событий, чтобы выполнять пользовательскую логику:
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
// src/EventListener/LogoutSubscriber.php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class LogoutSubscriber implements EventSubscriberInterface
{
public function __construct(
private UrlGeneratorInterface $urlGenerator
) {
}
public static function getSubscribedEvents(): array
{
return [LogoutEvent::class => 'onLogout'];
}
public function onLogout(LogoutEvent $event): void
{
// получить токен безопасности сесии, из которой сейчас будет выполнен выход
$token = $event->getToken();
// получить текущий запрос
$request = $event->getRequest();
// получить текущий ответ, если он уже установлен другим слушателем
$response = $event->getResponse();
// сконфигурировать пользовательский ответ выхода из системы на домашнюю страницу
$response = new RedirectResponse(
$this->urlGenerator->generate('homepage'),
RedirectResponse::HTTP_SEE_OTHER
);
$event->setResponse($response);
}
}
Настройка пути выхода
Другая опция - конфигурация path
в качестве имени маршрута. Это может быть полезно
если вы хотите, чтобы URI выхода из системы были динамическими (например, переводились в соответствии с
текущей локалью). В этом случае вам придется создать этот маршрут самостоятельно:
1 2 3 4 5 6
# config/routes.yaml
app_logout:
path:
en: /logout
fr: /deconnexion
methods: GET
Затем передайте имя маршрута в опцию path
1 2 3 4 5 6 7 8 9
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
logout:
path: app_logout
Извлечение объекта пользователя
После аутентификации, объект User
текущего пользователя доступен
через ярлык getUser()
в базовом контроллере :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class ProfileController extends AbstractController
{
public function index(): Response
{
// обычно вы хотите сначала убедиться, что пользователь аутентифивирован
// см. "Authorization" ниже
$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());
}
}
Извлечение пользователя из сервиса
Если вам нужно получить залогиненого пользователя из сервиса, используйте сервис Security:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// src/Service/ExampleService.php
// ...
use Symfony\Bundle\SecurityBundle\Security;
class ExampleService
{
// Избегайте вызова getUser() в констуркторе: авторизация может быть еще
// не выполнена. Вместо этого, сохраните весь объект Security.
public function __construct(
private Security $security,
){
}
public function someMethod(): void
{
// возвращает объект 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 %}
Контроль доступа (Авторизация)
Теперь пользователи могут выполнять вход в ваше приложения, используя форму входа. Отлично! Далее, вам нужно узнать, как отказывать в доступе и работать с объектом Пользователя. Это называется авторизация, и ее работа - решить, может ли пользователь получить доступ к какому-то источнику (URL, объекту model, методу вызова, ...).
Процесс авторизации имеет две стороны:
- Пользователь получает конкретную роль при выполнении входа (например,
ROLE_ADMIN
). - Вы добавляет код, чтобы источник (например, URL, контроллер) требовал конкретный "атрибут"
(например, роль типа
ROLE_ADMIN
), перед тем, как стать доступным.
Роли
Когда пользователь выполняет вход, Symfony вызывает метод getRoles()
в
вашем объекте User
, чтобы определить, какие роли имеет пользователь. В
классе User
, который был сгенерирован ранее, роли - это массив, хранящийся
в БД, и каждый пользователь всегда имеет хотя бы одну роль: ROLE_USER
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Entity/User.php
// ...
class User
{
#[ORM\Column(type: 'json')]
private array $roles = [];
// ...
public function getRoles(): array
{
$roles = $this->roles;
// гарантировать, что каждый пользователь имеет хотя бы ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
}
Это хорошо по умолчанию, но вы можете сделать что угодно, чтобы определить,
какие роли должен иметь пользователь. Единственное правило - каждая роль
должна начинаться с префикса ROLE_
- иначе, все не будет работать, как
ожидается. Кроме этого, роль - это просто строка, и вы можете придумать все,
что вам нужно (например, ROLE_PRODUCT_ADMIN
).
Вы будете использовать роли далее, чтобы предоставлять доступ к конкретным разделам вашего сайта.
Иерархичные роли
Вместо предоставления каждому пользователю множества ролей, вы можете определить правила наследования, создав иерархию ролей:
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]
Пользователи с ролью ROLE_ADMIN
будут также иметь роль ROLE_USER
.
Пользователи с ROLE_SUPER_ADMIN
, будут автоматически иметь ROLE_ADMIN
,
ROLE_ALLOWED_TO_SWITCH
и ROLE_USER
(наследуемые из ROLE_ADMIN
).
Caution
Для того, чтобы иерархия ролей работала, не используйте $user->getRoles()
вручную.
Например, в контроллере, расщиряющемся из базового контроллера :
1 2 3 4 5 6
// ПЛОХО - $user->getRoles() не будет знать об иерархии ролей
$hasAccess = in_array('ROLE_ADMIN', $user->getRoles());
// ХОРОШО - использование нормальных методов безопасности
$hasAccess = $this->isGranted('ROLE_ADMIN');
$this->denyAccessUnlessGranted('ROLE_ADMIN');
Note
Значения role_hierarchy
статические - вы не можете, к примеру, хранить иерархию
ролей в БД. Если вам нужно это, создайте пользовательский избиратель безопасности,
который ищет роли пользователей в БД.
Добавьте код для отказа в доступе
Существует два способа отказать в доступе к чему-то:
- access_control в security.yaml
позволяет вам защитить паттерны URL (например,
/admin/*
). Проще, но менее гибко; - в вашем контроллере (или другом коде) .
Защита паттернов URL (access_control)
Самый базовый способ защитить часть вашего приложения - защитить весь паттерн URL
в security.yaml
. Например, чтобы требовать ROLE_ADMIN
для всех URL, которые
начинаются с /admin
, вы можете:
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 }
Вы можете определить столько паттернов URL, сколько вам нужно - каждый будет регулярным выражением. НО, только один будет сопоставлен с каждым запросом: Symfony начинает сверху списка и останавливается, когда находит первое совпадение:
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 }
Добавление в начале пути ^
, означает, что только URL, которые начинаются
с паттерная, будут сопоставляться. Например, путь /admin
(без ^
)
совпадет с /admin/foo
, но кроме этого и с URL вроде /foo/admin
.
Каждый access_control
может также сопоставляться с IP-адресом, именем хостинга
и HTTP-методами. Он также может быть использован для перенаправления пользователя на
https
версию паттерна URL.
Безопасность контроллеров и другого кода
Вы можете отказать в доступе изнутри контроллера:
1 2 3 4 5 6 7 8 9 10
// 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, и никакой код в вашем контроллере больше не вызывается. Затем, происходит одно из двух:
- Если пользователь еще не вошел в систему, его попросят войти (например, перенаправят на страницу входа).
- Если пользователь уже в системе, но не имеет роли
ROLE_ADMIN
, ему отобразится ошибка доступа 403 (которую вы можете настроить ).
Еще одним способом обезопасить одно или более действий контроллера является использование
атрибута. В следующем примере, все действия контроллера будут требовать разрешения
ROLE_ADMIN
, кроме adminDashboard()
, который будет требовать разрешения
ROLE_SUPER_ADMIN
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/AdminController.php
// ...
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
class AdminController extends AbstractController
{
// Опционально, вы можете установить пользовательское сообщение, которое будет отображено пользователю
#[IsGranted('ROLE_SUPER_ADMIN', message: 'You are not allowed to access the admin dashboard.')]
public function adminDashboard(): Response
{
// ...
}
}
Если вы хотите использовать пользовательский статус-код вместо стандартного
(403), это можно сделать, установив аргумент statusCode
:
1 2 3 4 5 6 7 8 9 10
// src/Controller/AdminController.php
// ...
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN', statusCode: 423)]
class AdminController extends AbstractController
{
// ...
}
Вы также можете установить внутренний код исключения
AccessDeniedException,
которое вызывается с помощью аргумента exceptionCode
:
// src/Controller/AdminController.php // ...
use SymfonyComponentSecurityHttpAttributeIsGranted;
#[IsGranted('ROLE_ADMIN', statusCode: 403, exceptionCode: 10010)] class AdminController extends AbstractController { // ... }
Контроль доступа в шаблонах
Если вы хотите проверить, имеет ли текущий пользователь определенную роль,
вы можете использовать встроенную хелпер-функцию is_granted()
в любом
шаблоне Twig:
1 2 3
{% if is_granted('ROLE_ADMIN') %}
<a href="...">Delete</a>
{% endif %}
Безопасность других сервисов
Вы можете проверить доступ в любом месте вашего кода, внедрив сервис
Security
. Например, предположим, что у вас есть сервис SalesReportManager
,
и вы хотите добавить дополнительные детали только для пользователей, имеющих роль
ROLE_SALES_ADMIN
:
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
// src/SalesReport/SalesReportManager.php
// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
+ use Symfony\Bundle\SecurityBundle\Security;
class SalesReportManager
{
+ public function __construct(
+ Security $security,
+ ) {
+ }
public function generateReport(): void
{
$salesData = [];
+ if ($this->security->isGranted('ROLE_SALES_ADMIN')) {
+ $salesData['top_secret_numbers'] = rand();
+ }
// ...
}
// ...
}
Если вы используете конфигурацию services.yaml по умолчанию ,
Symfony автоматически передаст security.helper
вашему сервису, благодаря автомонтированию
и подсказке Security
.
Вы также можете иметь сервис низлежащего уровня
AuthorizationCheckerInterface.
Он делает то же, что и Security
, но позволяет вам добавлять подсказку более
конкретного интерфейса.
Разрешение незащищенного доступа (т.н. анонимные пользователи)
Когда посетитель еще не вошел на ваш сайт, он рассматривается, как
"неаутентифицированный" и не имеет никаких ролей. Это заблокирует ему
доступ к вашим страницам, если вы определили правило access_control
.
В конфигурации access_control
вы можете использовать атрибут безопасности
PUBLIC_ACCESS
, чтобы исключить некоторые маршруты для неаутентифицированного
доступа (например, страницу входа):
1 2 3 4 5 6 7 8 9 10 11
# config/packages/security.yaml
security:
enable_authenticator_manager: true
# ...
access_control:
# разрешить неаутентифицированным полльзователям доступ к форме входа
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
# но требовать аутентификации для всех других админских маршрутов
- { path: ^/admin, roles: ROLE_ADMIN }
Разрешение доступа анонимным пользователям в пользовательском избирателе
Если вы используете пользовательский избиратель, вы можете разрешить анонимным пользователям доступ, проверив, не установлен ли в токене пользователь:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// src/Security/PostVoter.php
namespace App\Security;
// ...
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\User\UserInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class PostVoter extends Voter
{
// ...
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
// ...
if (!$token->getUser() instanceof UserInterface) {
// пользователь не аутентифицирован, например, позволить ему видеть
// только публичные посты
return $subject->isPublic();
}
}
}
Установка индивидуальных разрешений пользователей
Большинство приложений требуют более конкретных правил доступа. К примеру, пользователь должен иметь возможность редактировать только собственные комментарии в блоге. Избиратели позволяют вам писать любую бизнес-логику, необходимую вам для определения доступа. Использование этих избирателей схоже с проверками доступа, основанного на ролях, реализуемыми в предыдущих главах. Прочтите Как использовать избирателей для проверки разрешений пользователей, чтобы узнать, как реализовать собственного избирателя.
Проверка, выполнил ли пользователь вход (IS_AUTHENTICATED_FULLY)
Если вы хотите проверить только выполнил ли пользователь вход (но вам не важны роли), у вас есть два варианта.
Первый, если вы дали каждому пользователю ROLE_USER
, вы можете проверить
эту роль.
Другой - вы можете использовать специальный "атрибут" вместо роли:
1 2 3 4 5 6 7 8
// ...
public function adminDashboard(): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED');
// ...
}
Вы можете использовать IS_AUTHENTICATED_FULLY
везде, где используются роли:
вроде access_control
или в Twig.
IS_AUTHENTICATED_FULLY
не является ролью, но ведет себя как она, и каждый
пользователь, выполнивший вход, будет иметь его. На самом деле, существуют
специальные атрибуты вроде этого:
IS_AUTHENTICATED_REMEMBERED
: Все пользователи, выполнившие вход, имеют его, даже если они в системе из-за "куки запомнить меня". Даже если вы не используете функционал запомнить меня, вы можете использовать это, чтобы проверять, выполнил ли пользователь вход.IS_AUTHENTICATED_FULLY
: Похоже наIS_AUTHENTICATED_REMEMBERED
, но мощнее. Пользователи, которые в системе только из-за "куки запомнить меня", будут иметьIS_AUTHENTICATED_REMEMBERED
, но не будут иметьIS_AUTHENTICATED_FULLY
.IS_REMEMBERED
: Только пользователи, аутентифицированные с использованием функционала запомнить меня, (т.е. куки запомнить меня).IS_IMPERSONATOR
: Когда текущий пользователь имперсонализирует другого пользователя в этой сессии, атрибут будет совпадать.
Понимание, как обновляются пользователи из сессии
В конце каждого запроса (кроме случаев, когда ваш брандмауэр stateless
), ваш
объект User
сериализируется в сессию. В начале следующего запроса, он десериализируется
и затем передается вашему поставщику пользователей для "обновления" (например, запросов
Doctrine для свежего пользователя).
Затем, два объектаUser (изначальный из сессии и обновленный объект User)
"сравниваются", чтобы увидеть "равны" ли они. По умолчанию, базовый класс
AbstractToken
сравнивает возвратные значения методов getPassword()
,
getSalt()
и getUserIdentifier()
. Если какие-либо из них отличаются,
ваш пользователь выйдет из системы. Это мера безопасности, чтобы гарантировать,
что зловредные пользователи будут деаутентифицированы, если изменятся базовые
данные пользователя.
Однако, в некоторых случаях, этот процесс может вызвать неожиданные проблемы аутентификации. Если у вас проблемы с аутентификацией, это может быть потому, что вы успешно аутентифицируетесь, но сразу же теряете аутентиикацию после первого перенаправления.
В таком случае, пересмотрите логику сериализации (например, методы __serialize()
или
serialize()
) в вашем классе пользователя (если она есть), чтобы убедиться, что все необходимые поля
сериализуются.
Сравнение пользователей вручную с EquatableInterface
Или, если вам нужно больше контроля над процессом "сравнения пользователей", сделайте
так, чтобы ваш класс User реализовывал EquatableInterface.
После этого, ваш метод isEqualTo()
будет вызываться при сравнении пользователей, вместо
базовой логики.
События безопасности
Во время процесса аутентификации, запускаются несколько событий, которые позволяют вам подключаться к процессу или настраивать отправленные пользователю ответы. Вы можете сделать это, создав для таких событий слушателя или подписчика событий.
Tip
Каждый брандмауэр Безопасности имеет собственный диспетчер событий
(security.event_dispatcher.FIREWALLNAME
). События запускаются как в
глобальном, так и в диспетчере брадмауэра. Вы можете зарегистрироваться
в диспетчере брандмауэра, если вы хотите, чтобы ваш слушатель вызывался
только для конкретного брандмауэра. Например, если у вас есть брандмауэры
api
и main
, используйте эту конфигурацию, чтобы регистрировать
только событие выхода из системы в брандмауэре main
:
1 2 3 4 5 6 7 8
# config/services.yaml
services:
# ...
App\EventListener\LogoutSubscriber:
tags:
- name: kernel.event_subscriber
dispatcher: security.event_dispatcher.main
События аутентификации
- CheckPassportEvent
- Запускается после того, как аутентификатор создал паспорт безопасности . Слушатели этого события проводят реальные проверки аутентификации (вроде проверки паспорта, валидации CSRF-токена и т.д.)
- AuthenticationTokenCreatedEvent
- Запускается после валидации паспорта и того, как аутентификатор создал токен безопасности (и пользователя). Это может быть использовано в продвинутых случаях применения, где вам нужно изменять созданный токен (например, для мульти-факторной аутентификации).
- AuthenticationSuccessEvent
-
Запускается когда аутентификация приближается к успеху. Это последнее событие,
которое может привести к неудачной аутентификации, вызвав
AuthenticationException
. - LoginSuccessEvent
- Вызывается после того, как аутентификация была полностью успешна. Слушатели этого события могут изменять ответ, отправленный пользователю.
- LoginFailureEvent
-
Запускается после вызова
AuthenticationException
во время аутентификации. Слушатели этого события изменяют ответ ошибки и отправляют его обратно пользователю.
Другие события
- InteractiveLoginEvent
- Развертывается после успешной аутентификации только в том случае, если аутентификатор реализует InteractiveAuthenticatorInterface, который указывает на то, что для входа в систему требуется явное действие пользователя (например, форма входа). Слушатели этого события могут изменять ответ, отправляемый пользователю.
- LogoutEvent
- Запускается прямо перед тем, как пользователь выходит из вашего приложения. См. .
- TokenDeauthenticatedEvent
- Запускается когда пользователь деаутентифицирован, например, из-за изменения пароля. См. Поставщики пользователей Безопасности.
- SwitchUserEvent
- Запускается после завершения "имитации другого". См. Как имперсонализировать пользователя.
Часто задаваемые вопросы
- У меня может быть много брандмауэров?
- Да! Но обычно это не нужно. Каждый брандмауэр - это как отдельная система безопасности, аутентификации в одном не делает вас аутентифицированным в другом. Один брандмауэр может иметь множество способов разрешения аутентификации (например, форму входа, аутентификацию ключа API и LDAP).
- Безопасность, похоже, не работает на моих страницах ошибок
- Так как маршрутизация проводится до безопасности, страницы ошибок 404 не охватываются ни одним брандмауэром. Это означает, что вы не можете проверять безопасность или даже получить доступ к объекту пользователя на таких страницах. См. Как настроить страницы ошибок, чтобы узнать больше.
- Похоже, моя аутентификация не работает: ошибок нет, но я не могу войти
-
Иногда аутентификация может быть успешной, но после перенаправления вы сразу же
выходите из системы, из-за проблемы с загрузкой
User
из сессии. Чтобы увидеть, в этом ли проблема, проверьте ваш файл логов (var/log/dev.log
) на предмет сообщения логов: - Не могу обновить токен, так как пользователь изменился
- Если вы видите это, есть два варианта, почему это так. Во-первых, может быть проблема с загрузкой вашего Пользователя из сессии. См. Поставщики пользователей Безопасности. Во-вторых, если определенная информация пользователя изменилась в БД с момента последнего обновления страницы, Symfony специально выведет пользователя из системы из соображений безопасности.
Узнайте больше
Аутентификация (Идентификация пользователя/вход в систему)
- Хеширование и верификация паролей
- Аутентификация с LDAP-сервером
- Как добавить функциональность входа в систему "Запомнить меня"
- Как имперсонализировать пользователя
- Как создавать и подключать пользовательские программы проверки пользователя
- Как ограничить брандмауэры по конкретному запросу
- Как реализовать CSRF-защиту
- Как настроить ответы аутентификатора формы входа
- Как создать пользовательский аутентификатор
- Точка входа: помощь пользователям с началом аутентификации