Як створити користувацький аутентифікатор
Дата оновлення перекладу 2024-06-03
Як створити користувацький аутентифікатор
Symfony постачається з багатьма аутентифікаторами і сторонні пакети також реалізують складніші випадки, на кшталт JWT та oAuth 2.0. Однау, іноді вам потрібно реалізувати користувацький механізм аутентифікації, який ще не існує, або вам потрібно налаштувати все існуючий. У таких випадках ви повинні створити та використати власний аутентифікатор.
Аутентифікатори повинні реалізовувати
AuthenticatorInterface.
Ви також можете розширити
AbstractAuthenticator,
який має реалізацію за замовчуванням для методу createAuthenticatedToken()
, що
підходить для більшості випадків застосування:
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
// 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');
}
return new SelfValidatingPassport(new UserBadge($apiToken));
}
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);
}
}
Tip
Якщо ваш користувацький аутентифікатор є формою входу, ви можете також розширити з класу AbstractLoginFormAuthenticator, щоб полегшити собі роботу.
Аутентифікатор можна підключити, використовуючи налаштування custom_authenticators
:
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 "Неавторизовано" у маршрутах 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($email), $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
);
}
}
Наступні класи ідентифікаційних даних підтримуються за замовчуваннямм:
- 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'));
}
}