Использование новой безопасности, основанной на аутентификаторе
Дата обновления перевода 2021-09-29
Использование новой безопасности, основанной на аутентификаторе
5.1
Безопасность, основанная на аутентификаторе, была представлена в Symfony 5.1.
В Symfony 5.1 была представлена новая система аутентификации. Эта система изменяет внутренности Безопасности Symfony, чтобы сделать ее более расширяемой и более понимаемой.
Подключение системы
Система, основанная на аутентификации, может быть подключена
с использованием настройки enable_authenticator_manager
:
1 2 3 4
# config/packages/security.yaml
security:
enable_authenticator_manager: true
# ...
Новая система обратно совместима с текущей системой аутентификации, с некоторыми исключениями, которые будут разъясняться в этой статье:
Добавление поддержки для небезопасного доступа (т.е. анонимные пользователи)
В Symfony, пользователи, которые еще не выполнили вход на ваш сайт, назывались
анонимными пользователями . Новая система больше
не имеет анонимной аутентификации. Вместо этого, такие сессии теперь рассматриваются,
как неаутентифицированные (т.е. без токена безопасности). При использовании
isGranted()
, результат всегда будет false
(т.е. отказ), так как эта сессия
рассматривается, как пользователь без привелегий.
В конфигурации access_control
вы можете использовать новый атрибут
безопасности PUBLIC_ACCESS
, чтобы поставить в лист ожидания некоторые
маршруты для неаутентифицированного доступа (например, страницу входа в систему):
1 2 3 4 5 6 7 8 9 10 11
# config/packages/security.yaml
security:
enable_authenticator_manager: true
# ...
access_control:
# разрешить неаутентифицированным пользователям доступ к форме входа
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
# но потребовать аутентификации для всех других маршрутов админа
- { path: ^/admin, roles: ROLE_ADMIN }
Предоставление доступа анонимным пользователям к пользовательскому избирателю
5.2
Класс NullToken
был представлен в Symfony 5.2.
Если вы используете пользовательского избирателя, вы можете предоставить анонимным пользователям доступ, проверив наличие специального NullToken. Этот токен используется в избирателях для представления неаутентифицированного доступа:
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/PostVoter.php
namespace App\Security;
// ...
use Symfony\Component\Security\Core\Authentication\Token\NullToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class PostVoter extends Voter
{
// ...
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
// ...
if ($token instanceof NullToken) {
// пользователь не аутентифицирован, например, позволить ему просмотр
// только публичных постов
return $subject->isPublic();
}
}
}
Конфигурация точки входа аутентификации
Иногда, один брандмауэр имеет несколько способов аутентификации (например, форму входа в систему и аутентификацию API-токена). В таких случаях, теперь необходимо сконфигурировать точку входа аутентификации. Точка входа используется для генерирования ответа, когда пользователь еще не аутентифицирован, но пытается получить доступ к странице, требующей аутентификации. Это может быть использовано, к примеру, для перенаправления пользователя на страницу входа.
Вы можете сконфигурировать это, используя настройку entry_point
:
1 2 3 4 5 6 7 8 9 10 11 12 13
# config/packages/security.yaml
security:
enable_authenticator_manager: true
# ...
firewalls:
main:
# позволить аутентификацию используя форму или базовый HTTP
form_login: ~
http_basic: ~
# сконфигурировать форму аутентификации в качестве точки входа для неаутентифицированных пользователей
entry_point: form_login
Note
Вы также можете создать собственную точку входа аутентификации, создав класс,
который реализует
AuthenticationEntryPointInterface.
Вы затем можете установить entry_point
в id сервиса (например,
entry_point: App\Security\CustomEntryPoint
)
Создание пользовательского аутентификатора
Безопасность традиционно можно расширить, написав пользовательских поставщиков аутентификации. Система, основанная на аутентификаторе, отменила поддержку этих провайдеров, и представила новый интерфейс аутентификатора в качестве основы для методов пользовательской аутентификации.
Tip
Аутентификаторы Guard все еще поддерживаются в системе, основанной на аутентификаторах. Однако, рекомендуется также обновить их, если вы реорганизуете свое приложение в новую систему. Новый интерфейс аутентификтора имеет много сходств с интерфейсом аутентификтора guard, что делает переписывание кода легче.
Аутентификаторы должны реализовывать
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\PassportInterface;
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): PassportInterface
{
$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);
}
}
Аутентификатор может быть подключен с использованием настройки custom_authenticators
:
1 2 3 4 5 6 7 8 9 10 11 12 13
# config/packages/security.yaml
security:
enable_authenticator_manager: true
# ...
firewalls:
main:
custom_authenticators:
- App\Security\ApiKeyAuthenticator
# не забудьте также сконфигурировать entry_point, если
# аутентификатор реализует AuthenticationEntryPointInterface
# entry_point: App\Security\CustomFormLoginAuthenticator
Метод authenticate()
- наиболее важный метод аутентификатора. Его работа
заключается в извлечении идентификационных данных (например, имя пользователя
и пароль, или API-токены) из объекта Request
и преобразовании их в безопасность
Passport.
Tip
Если вы хотите настроить форму входа, вы также можете расширить ее из класса AbstractLoginFormAuthenticator.
Паспорта безопасности
5.2
UserBadge
был представлен в Symfony 5.2. До версии 5.2, экземпляр
пользователя предоставлялся напрямую паспорту.
Паспорт - это объект, содержащий пользователя, который будет аутентифицирован, а также других частей информации, вроде должен ли быть проверен пароль, или должна ли быть подключена функция "запомнить меня".
По умолчанию, Passport требует пользователя и идентификационных данных.
Используйте
UserBadge,
чтобы присоединить пользователя к паспорту. UserBadge
требует идентификатор
пользователя (например, имя пользователя или адрес электронной почты), который
используется для загрузки пользователя при использовании поставщика пользователей :
1 2 3 4
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
// ...
$passport = new Passport(new UserBadge($email), $credentials);
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 26 27
// src/Security/CustomAuthenticator.php
namespace App\Security;
use App\Repository\UserRepository;
// ...
class CustomAuthenticator extends AbstractAuthenticator
{
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function authenticate(Request $request): PassportInterface
{
// ...
return new Passport(
new UserBadge($email, function ($userIdentifier) {
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
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; // ... return new Passport(new UserBadge($email), new CustomCredentials( // Если эта функция возвращает что-либо, кроме `true`, идентификационные данные // помечаются, как не валидные. // Параметр $credentials равняется следующему аргументу этого класса function ($credentials, UserInterface $user) { return $user->getApiToken() === $credentials; }, // Пользовательские идентификационные данные $apiToken ));
Паспорт самовалидации
Если вам не нужно проверять идентификационные данные (например, при
использовании токенов API), вы можете использовать
SelfValidatingPassport.
Этот класс требует только объект UserBadge
, и, по желанию, Знаки паспорта.
Знаки паспорта
Passport
также дополнительно позволяет вам добавлять знаки безопасности.
Знаки добавляют паспорту больше данных (чтобы расширить безопасность). По умолчанию,
следующие знаки поддерживаются:
- RememberMeBadge
-
Когда этот знак добавляется к паспорту, аутентификатор обозначает, что поддерживается
"запомнить меня". Используется ли на самом деле "запомнить меня", зависит от специальной
конфигурации
remember_me
. Прочтите Как добавить функциональность входа в систему "Запомнить меня", чтобы узнать больше. - PasswordUpgradeBadge
- Это используется для автоматического обновления пароля до нового хеша после успешного входа в систему. Этот знак требует простого текстового пароля и установщика обновлений паролей (например, хранилище пользователей). См. Как мигрировать хеш пароля.
- CsrfTokenBadge
- Автоматически валидирует CSRF-токены для этого аутентификатора во время аутентификации. Конструктор требует ID токена (уникальны для каждой формы) и CSRF-токен (уникальный для каждого запроса). См. Как реализовать CSRF-защиту.
- PreAuthenticatedUserBadge
- Означает, что этот пользователь был предварительно аутентифицирован (т.е. до запуска Symfony). Пропускает пред-аутентификационную проверку пользователей.
5.2
С версии 5.2, 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 27
// 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;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
class LoginAuthenticator extends AbstractAuthenticator
{
public function authenticate(Request $request): PassportInterface
{
$password = $request->request->get('password');
$username = $request->request->get('username');
$csrfToken = $request->request->get('csrf_token');
// ... валидировать, что ни один параметр не пуст
return new Passport(
new UserBadge($username),
new PasswordCredentials($password),
[new CsrfTokenBadge('login', $csrfToken)]
);
}
}
Tip
Кроме знаков, паспорта могут определять атрибуты, которые позволяют
методу authenticate()
хранить произвольную информацию в паспорте,
чтобы получить доступ к ней из других методов аутентификатора (например,
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
// ...
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class LoginAuthenticator extends AbstractAuthenticator
{
// ...
public function authenticate(Request $request): PassportInterface
{
// ... обработать запрос
$passport = new SelfValidatingPassport(new UserBadge($username), []);
// установить пользовательский атрибут (например, диапазон)
$passport->setAttribute('scope', $oauthScope);
return $passport;
}
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
{
// прочитать значение атрибута
return new CustomOauthToken($passport->getUser(), $passport->getAttribute('scope'));
}
}
5.2
Атрибуты паспортов были представлены в Symfony 5.2.