Як створити користувацький аутентифікатор
Дата оновлення перекладу 2025-09-15
Як створити користувацький аутентифікатор
Symfony постачається з багатьма аутентифікаторами і сторонні пакети також реалізують складніші випадки, на кшталт JWT та oAuth 2.0. Однак, іноді вам потрібно реалізувати користувацький механізм аутентифікації, який ще не існує, або вам потрібно налаштувати вже існуючий.
Щоб зекономити час, ви можете встановити Symfony Maker і дозволити Symfony згенерувати новий аутентифікатор, виконавши наступну команду:
1 2 3 4 5 6 7 8 9
$ php bin/console make:security:custom
Яке імʼя класу аутентифікатора (наприклад, CustomAuthenticator):
> ApiKeyAuthenticator
updated: config/packages/security.yaml
created: src/Security/ApiKeyAuthenticator.php
Успіх!
Відкрийте файл src/Security/ApiKeyAuthenticator.php, створений цією командою,
і ви знайдете щось на кшталт:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
// src/Security/ApiKeyAuthenticator.php
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\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class ApiKeyAuthenticator extends AbstractAuthenticator
{
/**
* Викликається за кожним запитом, щоб вирішити, чи має бути використаний цей
* аутентифікатор для запиту. Повернення `false` призведе до пропуску цього аутентифікатора.
*/
public function supports(Request $request): ?bool
{
return $request->headers->has('X-AUTH-TOKEN');
}
public function authenticate(Request $request): Passport
{
$apiToken = $request->headers->get('X-AUTH-TOKEN');
if (null === $apiToken) {
// Заголовок токена був порожнім, аутентифікація буде невдалою, з HTTP
// статус-кодом 401 "Неавторизовано"
throw new CustomUserMessageAuthenticationException('No API token provided');
}
// реалізуйте вашу власну логіку, щоб отримати ідентифікатор користувача з `$apiToken`
// наприклад, шляхом пошуку користувача у БД, використовуючи його ключ API
$userIdentifier = /** ... */;
return new SelfValidatingPassport(new UserBadge($userIdentifier));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// за умови успіху, дозволити запиту продовжити
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$data = [
// ви можете захотіти налаштувати або приховати повідомлення для початку
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// або перекласти це повідомлення
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
}
Аутентифікатори мають реалізовувати
AuthenticatorInterface.
Ви також можете розширити
AbstractAuthenticator,
що надає реалізацію методу createToken() за замовчуванням, яка
підходить для більшості випадків використання.
Tip
Якщо ваш користувацький аутентифікатор є формою входу, ви можете також розширити з класу AbstractLoginFormAuthenticator, щоб полегшити собі роботу.
Користувацькі аутентифікатори мають бути явно ввімкнені у конфігурації безпеки,
з використанням налаштування custom_authenticators у вашому(их) брандмауері(ах).
Якщо ви використовували команду make:security:custom, ця конфігурація вже
оновлена, але ви маєте її переглянути:
1 2 3 4 5 6 7 8
# config/packages/security.yaml
security:
# ...
firewalls:
main:
custom_authenticators:
- App\Security\ApiKeyAuthenticator
Tip
Ви можете захотіти, щоб ваш аутентифікатор реалізовуваа
AuthenticationEntryPointInterface. Це визначає відповідь, відправлену
користувачам для початку аутентифікації (наприклад, коли вони відвідують
захищену сторінку). Прочитайте більше про це у Точка входу: допомога користувачам з початком аутентифікації.
Метод authenticate() - це найважливіший метод аутентифікатора. Його робота полягає
у вилученні інформації ідентифікаційних даних (наприклад, імені користувача та паролю,
або API-токенів) з обʼєкта Request, та їх перетворенні на безпечний
Passport
(паспорти безпеки розʼяснюються далі у цій статті).
Після того, як процес аутентифікації буде завершено, користувач або буде аутентифікований, або щось пішло не так (наприклад, неправильний пароль). Аутентифікатор може визначити, що відбуватиметься у цих випадках:
onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response-
Якщо аутентифікація пройшла успішно, цей метод викликається з аутентифікованим
$token.Цей метод може повертати відповідь (наприклад, перенаправляти користувача на деяку сторінку).
Якщо повертається
null, запит продовжується як звичайно (тобто, викликається контролер, що співпадає з маршрутом входу у систему). Це корисно для маршрутів API, де кожний маршрут захищено заголовком ключа API. onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response-
Якщо аутентифікація не вдалася (наприклад, невірний пароль), цей метод
викликається зAuthenticationException.Цей метод може повернути відповідь (наприклад, надіслати 401 Unauthorized у маршрутах API).
Якщо повертається
null, запит продовжується як звичайно. Це корисно, наприклад, для форм входу у систему, де контролер входу у систему запускаєть знову, з помилками входу у систему.Якщо ви використовуєте тротлінг входу у систему , ви можете перевірити, чи є
$exceptionекземпляром TooManyLoginAttemptsAuthenticationException (наприклад, щоб відобразити відповідне повідомлення).Увага: Ніколи не використовуйте
$exception->getMessage()для екземплярівAuthenticationException. Це повідомлення може містити чутливу інформацію, яку ви не хочете демонструвати публічно. Замість цього, викорисовуйте$exception->getMessageKey()і$exception->getMessageData(), як продемонстровано у повному прикладі вище. Використайте CustomUserMessageAuthenticationException, якщо хочете встановлювати користувацькі повідомлення про помилки.
Tip
Якщо ваш метод входу у систему інтерактивний, що означає, що користувач активно входить у ваш додаток, ви можете захотіти, щоб ваш аутентифікатор реалізовував InteractiveAuthenticatorInterface, щоб він розгортав InteractiveLoginEvent
Паспорти безпеки
Паспорт - це обʼєкт, який містить користувача, який буде аутентифікований, а також інші частини інформації, на кшталт того, чи має бути перевірений пароль, або чи має бути підключена функція "запамʼятати мене".
За замовчуванням, Passport вимагає користувача та ідентифікаційні дані (наприклад, пароль).
Використайте UserBadge,
щоб приєднати користувача до паспорта. UserBadge вимагає ідентифікатор користувача
(наприклад, імʼя користувача або адресу електронної пошти):
1 2 3 4
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
// ...
$passport = new Passport(new UserBadge($userIdentifier), $credentials);
Ідентифікатор користувача
Ідентифікатор користувача - це унікальний рядок, який ідентифікує користувача. Часто це
щось на кшталт його електронної адреси або імені користувача, але це може бути будь-яке
унікальне значення, пов'язане з користувачем. Воно дозволяє завантажити користувача через
сконфігурований постачальник користувачів .
Note
Максимально дозволена довжина ідентифікатора користувача складає 4096 символів, щоб запобігти атакам переповнення сховища сесій.
Note
Ви можете опціонально передати завантажувач користувача як другий аргумент до
UserBadge. Це викличне отримує $userIdentifier і має повернути об'єктUserInterface (інакше буде викликано виключення UserNotFoundException):
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/Security/CustomAuthenticator.php
namespace App\Security;
use App\Repository\UserRepository;
// ...
class CustomAuthenticator extends AbstractAuthenticator
{
public function __construct(
private UserRepository $userRepository,
) {
}
public function authenticate(Request $request): Passport
{
// ...
return new Passport(
new UserBadge($email, function (string $userIdentifier): ?UserInterface {
return $this->userRepository->findOneBy(['email' => $userIdentifier]);
}),
$credentials
);
}
}
Деякі додатки нормалізують ідентифікатори користувачів перед їх обробкою. Наприклад,
переведення ідентифікаторів у нижній регістр допомагає розглядати такі значення, як "john.doe",
"John.Doe" або "JOHN.DOE" як еквівалентні в системах, де ідентифікатори не чутливі до регістру.
За потреби ви можете передати нормалізатор як третій аргумент до UserBadge.
Це викличне отримує $userIdentifier і має повернути рядок.
7.3
Підтримка нормалізаторів ідентифікаторів користувачів була представлена в Symfony 7.3.
Приклад нижче використовує нормалізатор, який перетворює імена користувачів на нормалізований, виключно ASCII, формат в нижньому регістрі:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Security/NormalizedUserBadge.php
namespace App\Security;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use function Symfony\Component\String\u;
final class NormalizedUserBadge extends UserBadge
{
public function __construct(string $identifier)
{
$callback = static fn (string $identifier): string => u($identifier)->normalize(UnicodeString::NFKC)->ascii()->lower()->toString();
parent::__construct($identifier, null, $callback);
}
}
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/PasswordAuthenticator.php
namespace App\Security;
final class PasswordAuthenticator extends AbstractLoginFormAuthenticator
{
// спрощено для стислості
public function authenticate(Request $request): Passport
{
$username = (string) $request->request->get('username', '');
$password = (string) $request->request->get('password', '');
$request->getSession()
->set(SecurityRequestAttributes::LAST_USERNAME, $username);
return new Passport(
new NormalizedUserBadge($username),
new PasswordCredentials($password),
[
// всі інші корисні бейджі
]
);
}
}
Повноваження користувача
Повноваження користувача використовуються для аутентифікації користувача, тобто для
перевірки валідності наданої інформації (такої як пароль, токен API або користувацькі
повноваження).
Наступні класи ідентифікаційних даних підтримуються за замовчуваннямм:
- PasswordCredentials
-
Це вимагає простого текстового
$password, який валідується з використанням кодувальника паролів, сконфігурованого для користувача :1 2 3 4
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; // ... return new Passport(new UserBadge($email), new PasswordCredentials($plaintextPassword)); - CustomCredentials
-
Дозволяє користувацькому замиканню перевіряти ідентифікаційні дані:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; // ... return new Passport(new UserBadge($email), new CustomCredentials( // Якщо ця функція повертає будь-що, окрім `true`, ідентифікаційні дані // відмічаються як невалідні. // Параметр $credentials дорівнює наступному аргументу цього класу function (string $credentials, UserInterface $user): bool { return $user->getApiToken() === $credentials; }, // Користувацькі ідентифікаційні дані $apiToken ));
Паспорт самовалідації
Якщо вам не потрібно перевіряти ідентифікаційні дані (наприклад, при використанні
токенів API), ви можете використати
SelfValidatingPassport.
Це клас вимагає лише обʼєкт UserBadge, і, за бажанням, Знаки паспорта.
Бейджі паспорта
Passport також додатково дозволяє вам додавати знаки безпеки. Знаки додають паспорту
більше даних (щоб розширити безпеку). За замовчуванням, наступні знаки підтримуються:
- RememberMeBadge
-
Коли цей знак додається до паспорту, аутентифікатор відзначає, що підтримується
"запамʼятати мене". Чи використовується насправді "запамʼятати мене", залежить від
спеціальної конфігурації
remember_me. Прочитайте Як додати функціональність входу у систему "Запамʼятати мене", щоб дізнатися більше. - PasswordUpgradeBadge
-
Це використовується для автоматичного оновлення пароля до нового хешу після успішного
входу у систему. Цей знак вимагає простого текстового пароля та установника оновлень
паролів (наприклад, сховище користувачів). Див. Як мігрувати хеш пароля. - CsrfTokenBadge
- Автоматично валідує CSRF-токени для цього аутентифікатора під час аутентифікації. Конструктор вимагає ID токена (унікальні для кожної форми) та CSRF-токен (унікальний для кожного запиту). Див. Як реалізувати CSRF-захист.
- PreAuthenticatedUserBadge
- Означає, що цей користувач був попередньо аутентифікований (тобто, до запуску Symfony). Пропускає перед-аутентифікаційну перевірку користувачів.
Note
PasswordUpgradeBadge автоматично додається до паспорту, якщо паспорт має
PasswordCredentials.
Наприклад, якщо ви хочете додати CSRF до вашого користувацького аутентифікатора, ви запустите паспорт таким чином:
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/Service/LoginAuthenticator.php
namespace App\Service;
// ...
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
class LoginAuthenticator extends AbstractAuthenticator
{
public function authenticate(Request $request): Passport
{
$password = $request->getPayload()->get('password');
$username = $request->getPayload()->get('username');
$csrfToken = $request->getPayload()->get('csrf_token');
// ...
return new Passport(
new UserBadge($username),
new PasswordCredentials($password),
[new CsrfTokenBadge('login', $csrfToken)]
);
}
}
Атрибути паспорта
Окрім знаків, паспорти можуть визначати атрибути, що дозволяє методу authenticate()
зберігати довільну інформацію у паспорті, щоб отримати до нього доступ з інших методів
аутентифікатора (наприклад, createToken()):
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
// ...
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class LoginAuthenticator extends AbstractAuthenticator
{
// ...
public function authenticate(Request $request): Passport
{
// ... обробити запит
$passport = new SelfValidatingPassport(new UserBadge($username), []);
// встановити користувацький атрибут (наприклад, scope)
$passport->setAttribute('scope', $oauthScope);
return $passport;
}
public function createToken(Passport $passport, string $firewallName): TokenInterface
{
// прочитати значення атрибута
return new CustomOauthToken($passport->getUser(), $passport->getAttribute('scope'));
}
}