Уточняю, что начинаю с Symfony. Я хочу создать API (без FOSRestBundle) с токеном в качестве средства аутентификации.
Я следовал разным руководствам по этой настройке. Я бы хотел, чтобы при обнаружении ошибки классом AuthTokenAuthenticator он возвращал json, а не представление html.
Вот мой сценарий:
AuthTokenAuthenticator
namespace AppBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
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\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\HttpFoundation\JsonResponse;
class AuthTokenAuthenticator implements
SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
const TOKEN_VALIDITY_DURATION = 12 * 3600;
protected $httpUtils;
public function __construct(HttpUtils $httpUtils)
{
$this->httpUtils = $httpUtils;
}
public function createToken(Request $request, $providerKey)
{
//$targetUrlToken = '/auth-tokens'; // login
//$targetUrlUser = '/users/create'; // create account
/*if ($request->getMethod() === "POST" && $this->httpUtils->checkRequestPath($request, $targetUrlUser) || $request->getMethod() === "POST" && $this->httpUtils->checkRequestPath($request, $targetUrlToken) ) {
return;
}*/
$authTokenHeader = $request->headers->get('X-Auth-Token');
if (!$authTokenHeader) {
//return new JsonResponse(array("error" => 1, "desc" => "INVALID_TOKEN", "message" => "X-Auth-Token header is required"));
throw new BadCredentialsException('X-Auth-Token header is required');
}
return new PreAuthenticatedToken(
'anon.',
$authTokenHeader,
$providerKey
);
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
if (!$userProvider instanceof AuthTokenUserProvider) {
throw new \InvalidArgumentException(
sprintf(
'The user provider must be an instance of AuthTokenUserProvider (%s was given).',
get_class($userProvider)
)
);
}
$authTokenHeader = $token->getCredentials();
$authToken = $userProvider->getAuthToken($authTokenHeader);
if (!$authToken || !$this->isTokenValid($authToken)) {
throw new BadCredentialsException('Invalid authentication token');
}
$user = $authToken->getUser();
$pre = new PreAuthenticatedToken(
$user,
$authTokenHeader,
$providerKey,
$user->getRoles()
);
$pre->setAuthenticated(true);
return $pre;
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
/**
* Vérifie la validité du token
*/
private function isTokenValid($authToken)
{
return (time() - $authToken->getCreatedAt()->getTimestamp()) < self::TOKEN_VALIDITY_DURATION;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
throw $exception;
}
}
Вот сообщение об ошибке, которое у меня есть, когда я не сообщаю токен:
<!DOCTYPE html>
<html>
<head>
<title> X-Auth-Token header is required (500 Internal Server Error)
Как я могу получить ответ json? Если я попытаюсь вернуть новый JsonResponse (array ("test" => "KO")) (простой пример), я получу эту ошибку:
<title> Type error: Argument 1 passed to Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager::authenticate() must be an instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface, instance of Symfony\Component\HttpFoundation\JsonResponse given, called in /Users/mickaelmercier/Desktop/workspace/api_monblocrecettes/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php on line 101 (500 Internal Server Error)
Вы пытались принудительно использовать формат json в определениях маршрутизации?

Вы можете создать свой собственный обработчик ошибок. Это прослушиватель событий или подписчик, который слушает kernel.exception, когда он добавляет ответ на событие, распространение события останавливается, поэтому обработчик ошибок по умолчанию не запускается.
Это могло выглядеть примерно так:
<?php declare(strict_types = 1);
namespace App\EventSubsbscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
final class ExceptionToJsonResponseSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => 'onKernelException',
];
}
public function onKernelException(GetResponseForExceptionEvent $event): void
{
// Skip if request is not an API-request
$request = $event->getRequest();
if (strpos($request->getPathInfo(), '/api/') !== 0) {
return;
}
$exception = $event->getException();
$error = [
'type' => $this->getErrorTypeFromException($exception),
// Warning! Passing the exception message without checks is insecure.
// This will potentially leak sensitive information.
// Do not use this in production!
'message' => $exception->getMessage(),
];
$response = new JsonResponse($error, $this->getStatusCodeFromException($exception));
$event->setResponse($response);
}
private function getStatusCodeFromException(\Throwable $exception): int
{
if ($exception instanceof HttpException) {
return $exception->getStatusCode();
}
return 500;
}
private function getErrorTypeFromException(\Throwable $exception): string
{
$parts = explode('\\', get_class($exception));
return end($parts);
}
}
ApiPlatform предоставляет свой собственный прослушиватель исключений, подобный этому, который является более продвинутым, и вы можете изучить его, если вам нужны «лучшие» ответы на исключения.
Действительно здорово! Это работает ! Мне нужно узнать больше о слушателе исключений. Спасибо
Пожалуйста, переведите введение на английский язык.