Symfony/Security для PHP: полное руководство с примерами
Symfony/Security — это мощный и гибкий компонент экосистемы Symfony, предназначенный для реализации аутентификации (проверки подлинности) и авторизации (проверки прав доступа) в PHP-приложениях. Он предоставляет разработчикам готовую архитектуру для защиты маршрутов, управления сессиями, работы с паролями и интеграции с различными провайдерами пользователей (база данных, LDAP, OAuth).
Зачем нужна эта библиотека? В современной веб-разработке безопасность — это не опция, а обязательное требование. Symfony/Security позволяет абстрагироваться от рутинных задач: хеширование паролей, создание форм входа, проверка ролей и токенов. Вместо написания велосипеда вы получаете промышленный стандарт, который используется в тысячах проектов по всему миру. Компонент легко интегрируется как с фреймворком Symfony, так и с другими PHP-приложениями через пакет symfony/security-core.
Основные возможности включают: поддержку нескольких "стен" (firewalls) для разных частей приложения, гибкую систему провайдеров пользователей, автоматическое управление сессиями и cookie, а также встроенную защиту от CSRF-атак и подбора паролей. Библиотека следует принципу "security by default", что делает её идеальным выбором для проектов любого масштаба.
Установка
Установка производится через Composer — стандартный менеджер пакетов для PHP. Выполните следующую команду в корне вашего проекта:
composer require symfony/security-bundle
Если вы используете Symfony Flex (рекомендуется), рецепт автоматически настроит базовую конфигурацию. Для standalone-проектов (без фреймворка) установите ядро:
composer require symfony/security-core symfony/security-http symfony/security-csrf
Быстрый старт: минимальный рабочий пример
Создадим простую защищённую страницу. Для начала убедитесь, что у вас установлен Symfony (версия 6.4+). Создадим контроллер и настроим безопасность.
Шаг 1: Конфигурация security.yaml
# config/packages/security.yaml
security:
enable_authenticator_manager: true
providers:
in_memory:
memory:
users:
admin:
password: '$2y$13$...' # хеш пароля "admin123"
roles: ['ROLE_ADMIN']
firewalls:
main:
lazy: true
provider: in_memory
form_login:
login_path: login
check_path: login
logout:
path: /logout
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
Шаг 2: Контроллер
// src/Controller/SecurityController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
#[Route('/login', name: 'login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
#[Route('/admin', name: 'admin')]
public function admin(): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
return new Response('Добро пожаловать в админ-панель!');
}
}
Шаг 3: Шаблон входа (Twig)
{# templates/security/login.html.twig #}
{% if error %}
{{ error.messageKey|trans(error.messageData, 'security') }}
{% endif %}
Основные методы, классы и функции
Ниже приведены ключевые компоненты библиотеки. Для каждого указана сигнатура, описание и пример.
1. PasswordHasherInterface::hash()
Сигнатура: string hash(#[\SensitiveParameter] string $plainPassword): string
Описание: Хеширует пароль с использованием алгоритма по умолчанию (bcrypt или sodium). Никогда не храните пароли в открытом виде.
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserService {
public function __construct(
private UserPasswordHasherInterface $passwordHasher
) {}
public function createUser(string $plainPassword): User {
$user = new User();
$hashedPassword = $this->passwordHasher->hashPassword($user, $plainPassword);
$user->setPassword($hashedPassword);
return $user;
}
}
2. UserPasswordHasherInterface::isPasswordValid()
Сигнатура: bool isPasswordValid(PasswordAuthenticatedUserInterface $user, #[\SensitiveParameter] string $plainPassword): bool
Описание: Проверяет, соответствует ли введённый пароль хешу, хранящемуся в объекте пользователя.
if ($this->passwordHasher->isPasswordValid($user, $inputPassword)) {
// Пароль верный
} else {
// Неверный пароль
}
3. Security::getUser()
Сигнатура: ?UserInterface getUser(): ?UserInterface
Описание: Возвращает текущего аутентифицированного пользователя или null, если пользователь не вошёл в систему.
use Symfony\Bundle\SecurityBundle\Security;
class ProfileController {
public function __construct(private Security $security) {}
public function showProfile(): Response {
$user = $this->security->getUser();
if (!$user) {
return new RedirectResponse('/login');
}
return new Response('Привет, ' . $user->getUserIdentifier());
}
}
4. AuthorizationCheckerInterface::isGranted()
Сигнатура: bool isGranted(mixed $attribute, mixed $subject = null): bool
Описание: Проверяет, имеет ли текущий пользователь определённое право (роль или атрибут).
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class AdminController {
public function __construct(private AuthorizationCheckerInterface $authChecker) {}
public function deleteUser(User $user): Response {
if (!$this->authChecker->isGranted('ROLE_ADMIN')) {
throw $this->createAccessDeniedException();
}
// удаление пользователя
}
}
5. AccessDeniedException
Сигнатура: class AccessDeniedException extends RuntimeException
Описание: Исключение, которое выбрасывается, когда у пользователя нет прав на выполнение действия. Symfony автоматически обрабатывает его, возвращая 403 ошибку.
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
throw new AccessDeniedException('Доступ запрещён!');
}
6. TokenStorageInterface::getToken()
Сигнатура: ?TokenInterface getToken(): ?TokenInterface
Описание: Возвращает текущий токен безопасности, который содержит информацию о пользователе и его ролях.
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class AuditService {
public function __construct(private TokenStorageInterface $tokenStorage) {}
public function logAction(): void {
$token = $this->tokenStorage->getToken();
if ($token) {
$user = $token->getUser();
// логируем действие пользователя
}
}
}
7. AuthenticationUtils::getLastAuthenticationError()
Сигнатура: ?AuthenticationException getLastAuthenticationError(): ?AuthenticationException
Описание: Извлекает последнюю ошибку аутентификации из сессии (полезно для отображения сообщений об ошибках в форме входа).
// В контроллере входа
$error = $authenticationUtils->getLastAuthenticationError();
if ($error) {
$this->addFlash('error', $error->getMessageKey());
}
8. UserInterface::getRoles()
Сигнатура: array getRoles(): array
Описание: Возвращает массив ролей, присвоенных пользователю. Роли должны начинаться с префикса ROLE_.
class User implements UserInterface {
private array $roles = ['ROLE_USER'];
public function getRoles(): array {
return $this->roles;
}
public function setRoles(array $roles): self {
$this->roles = $roles;
return $this;
}
}
9. LogoutUrlGenerator::getLogoutUrl()
Сигнатура: string getLogoutUrl(): string
Описание: Генерирует URL для выхода из системы с автоматическим CSRF-токеном.
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
class NavigationController {
public function __construct(private LogoutUrlGenerator $logoutUrlGenerator) {}
public function menu(): Response {
$logoutUrl = $this->logoutUrlGenerator->getLogoutUrl();
return $this->render('menu.html.twig', ['logout_url' => $logoutUrl]);
}
}
Реальный практический пример: Система управления задачами
Создадим защищённое приложение для управления задачами с разграничением прав (администратор и обычный пользователь).
1. Сущность User (Doctrine)
// src/Entity/User.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(type: 'json')]
private array $roles = ['ROLE_USER'];
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; }
public function getUserIdentifier(): string { return $this->email; }
public function getRoles(): array { return $this->roles; }
public function setRoles(array $roles): self { $this->roles = $roles; return $this; }
public function getPassword(): ?string { return $this->password; }
public function setPassword(string $password): self { $this->password = $password; return $this; }
public function eraseCredentials(): void {}
}
2. Конфигурация безопасности
# config/packages/security.yaml
security:
password_hashers:
App\Entity\User: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
enable_csrf: true
logout:
path: app_logout
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 7 дней
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/task, roles: ROLE_USER }
3. Контроллер задач
// src/Controller/TaskController.php
namespace App\Controller;
use App\Entity\Task;
use App\Repository\TaskRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class TaskController extends AbstractController
{
#[Route('/task/new', name: 'task_new')]
#[IsGranted('ROLE_USER')]
public function new(Request $request, EntityManagerInterface $em): Response
{
$task = new Task();
$form = $this->createForm(TaskType::class, $task);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$task->setUser($this->getUser());
$em->persist($task);
$em->flush();
return $this->redirectToRoute('task_list');
}
return $this->render('task/new.html.twig', [
'form' => $form->createView(),
]);
}
#[Route('/task/{id}/delete', name: 'task_delete')]
public function delete(Task $task, EntityManagerInterface $em): Response
{
// Только владелец или админ может удалить задачу
if ($task->getUser() !== $this->getUser() && !$this->isGranted('ROLE_ADMIN')) {
throw $this->createAccessDeniedException('Вы не можете удалить эту задачу.');
}
$em->remove($task);
$em->flush();
return $this->redirectToRoute('task_list');
}
#[Route('/admin/tasks', name: 'admin_tasks')]
#[IsGranted('ROLE_ADMIN')]
public function adminList(TaskRepository $taskRepository): Response
{
$tasks = $taskRepository->findAll();
return $this->render('task/admin_list.html.twig', [
'tasks' => $tasks,
]);
}
}
4. Voter для кастомной логики