Компоненты безопасности Symfony
Безопасность в Symfony построена на нескольких компонентах, работающих вместе.
Symfony 8.0: Компоненты Security Core, CSRF и PasswordHasher теперь явно перечислены в темах экзамена. Authenticators, Passports и Badges -- новые темы, которых не было в экзамене Sf7.4.
| Компонент | Назначение |
|---|---|
| Security Core | Ядро: токены, аутентификация, авторизация |
| Security HTTP | Firewall, аутентификаторы, entry points |
| Security CSRF | Защита от CSRF-атак |
| PasswordHasher | Хеширование паролей |
Архитектура аутентификации
HTTP Request
|
v
Firewall (security.yaml)
|
v
Authenticator (implements AuthenticatorInterface)
|
v
Passport (credentials + user + badges)
|
v
AuthenticationManager (validates passport)
|
v
Token (authenticated user stored in TokenStorage)
|
v
Response
Authenticator (Аутентификатор)
Аутентификатор -- класс, который отвечает за процесс аутентификации. Каждый аутентификатор реализует AuthenticatorInterface.
<?php
declare(strict_types=1);
namespace App\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
final class ApiTokenAuthenticator extends AbstractAuthenticator
{
public function __construct(
private readonly TokenRepository $tokenRepository,
) {
}
/**
* Called on every request -- decide if this authenticator should be used
*/
public function supports(Request $request): ?bool
{
return $request->headers->has('X-API-TOKEN');
}
/**
* Create a Passport with user info and credentials
*/
public function authenticate(Request $request): Passport
{
$apiToken = $request->headers->get('X-API-TOKEN');
if (null === $apiToken) {
throw new CustomUserMessageAuthenticationException(
'No API token provided'
);
}
return new Passport(
new UserBadge($apiToken, function (string $token): UserInterface {
$user = $this->tokenRepository->findUserByToken($token);
if (null === $user) {
throw new CustomUserMessageAuthenticationException(
'Invalid API token'
);
}
return $user;
}),
new SelfValidatingPassport() // No password check needed
);
}
public function onAuthenticationSuccess(
Request $request,
TokenInterface $token,
string $firewallName,
): ?Response {
// Return null to continue the request
return null;
}
public function onAuthenticationFailure(
Request $request,
AuthenticationException $exception,
): ?Response {
return new JsonResponse([
'error' => strtr(
$exception->getMessageKey(),
$exception->getMessageData()
),
], Response::HTTP_UNAUTHORIZED);
}
}
Методы AuthenticatorInterface
| Метод | Описание |
|---|---|
supports(Request) |
Нужно ли обрабатывать этот запрос? |
authenticate(Request) |
Создать Passport с данными пользователя |
onAuthenticationSuccess() |
Действие при успешной аутентификации |
onAuthenticationFailure() |
Действие при ошибке |
Passport (Паспорт)
Passport -- контейнер данных аутентификации. Содержит UserBadge, credentials и дополнительные badges.
<?php
declare(strict_types=1);
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
// Full passport with badges
$passport = new Passport(
// UserBadge -- identifies the user
new UserBadge($email),
// PasswordCredentials -- password to verify
new PasswordCredentials($password),
// Additional badges
[
new CsrfTokenBadge('authenticate', $csrfToken),
new RememberMeBadge(),
]
);
Типы Credentials
| Класс | Описание |
|---|---|
PasswordCredentials |
Проверка пароля через PasswordHasher |
CustomCredentials |
Пользовательская проверка (callback) |
SelfValidatingPassport
Для случаев, когда проверка учётных данных не требуется (API-токены, OAuth).
<?php
declare(strict_types=1);
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
// No credentials to check
$passport = new SelfValidatingPassport(
new UserBadge($apiToken, fn(string $token) => $this->findUser($token))
);
Badges (Бейджи)
Symfony 8.0: Badges -- новая тема экзамена. Badges добавляют дополнительные проверки и функционал к процессу аутентификации.
| Badge | Описание |
|---|---|
UserBadge |
Идентификация пользователя (обязательный) |
CsrfTokenBadge |
Проверка CSRF-токена |
RememberMeBadge |
Поддержка "Запомнить меня" |
PasswordUpgradeBadge |
Автоматическое обновление хеша пароля |
<?php
declare(strict_types=1);
namespace App\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\SecurityRequestAttributes;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
final class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
) {
}
public function authenticate(Request $request): Passport
{
$email = $request->getPayload()->getString('email');
$password = $request->getPayload()->getString('password');
$csrfToken = $request->getPayload()->getString('_csrf_token');
$request->getSession()->set(
SecurityRequestAttributes::LAST_USERNAME,
$email,
);
return new Passport(
new UserBadge($email),
new PasswordCredentials($password),
[
new CsrfTokenBadge('authenticate', $csrfToken),
new RememberMeBadge(),
new PasswordUpgradeBadge($password),
]
);
}
public function onAuthenticationSuccess(
Request $request,
TokenInterface $token,
string $firewallName,
): ?Response {
// Redirect to the page user tried to access before login
if ($targetPath = $this->getTargetPath(
$request->getSession(),
$firewallName
)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('app_home'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate('app_login');
}
}
CSRF-защита
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
final class DeleteController extends AbstractController
{
public function delete(Request $request): Response
{
$token = $request->getPayload()->getString('_token');
// Validate CSRF token
if (!$this->isCsrfTokenValid('delete-item', $token)) {
throw $this->createAccessDeniedException('Invalid CSRF token');
}
// Proceed with deletion
return $this->redirectToRoute('app_list');
}
}
В Twig-шаблоне:
<form method="post" action="{{ path('app_delete', {id: item.id}) }}">
<input type="hidden" name="_token" value="{{ csrf_token('delete-item') }}">
<button type="submit">Delete</button>
</form>
PasswordHasher
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
final class UserRegistrationService
{
public function __construct(
private readonly UserPasswordHasherInterface $passwordHasher,
private readonly EntityManagerInterface $entityManager,
) {
}
public function register(string $email, string $plainPassword): User
{
$user = new User();
$user->setEmail($email);
// Hash the password
$hashedPassword = $this->passwordHasher->hashPassword(
$user,
$plainPassword,
);
$user->setPassword($hashedPassword);
$this->entityManager->persist($user);
$this->entityManager->flush();
return $user;
}
public function verifyPassword(User $user, string $plainPassword): bool
{
return $this->passwordHasher->isPasswordValid($user, $plainPassword);
}
}
Конфигурация PasswordHasher
# config/packages/security.yaml
security:
password_hashers:
# Auto-select the best algorithm
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# Or specify explicitly
App\Entity\User:
algorithm: bcrypt
cost: 13
Подвох экзамена: Значение
'auto'для алгоритма хеширования выберет лучший доступный алгоритм (bcrypt на момент Sf8). В тестовом окружении рекомендуется использоватьplaintextилиsodiumс низкой стоимостью для скорости тестов.
UserInterface
<?php
declare(strict_types=1);
namespace App\Entity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
final class User implements UserInterface, PasswordAuthenticatedUserInterface
{
private ?int $id = null;
private string $email = '';
private string $password = '';
/** @var list<string> */
private array $roles = [];
public function getUserIdentifier(): string
{
return $this->email;
}
/** @return list<string> */
public function getRoles(): array
{
$roles = $this->roles;
// Every user has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function getPassword(): ?string
{
return $this->password;
}
public function eraseCredentials(): void
{
// Clear temporary sensitive data
// $this->plainPassword = null;
}
}
Итоги
- Аутентификация в Sf8 построена на Authenticators + Passport + Badges
supports()определяет, нужен ли данный аутентификаторauthenticate()создаёт Passport с данными пользователя- Passport содержит UserBadge + Credentials + дополнительные badges
- CsrfTokenBadge, RememberMeBadge, PasswordUpgradeBadge -- стандартные бейджи
- PasswordHasher хеширует и проверяет пароли
- UserInterface требует
getUserIdentifier(),getRoles(),eraseCredentials()