Symfony/Security для PHP: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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 для кастомной логики

Рекомендаций