279 lines
11 KiB
PHP
279 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator;
|
|
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Exception\ExpiredTokenException;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidPayloadException;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidTokenException;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Exception\MissingTokenException;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\Token\JWTPostAuthenticationToken;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\PayloadAwareUserProviderInterface;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
|
use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
|
|
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\UsernameNotFoundException;
|
|
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
|
use Symfony\Component\Security\Core\User\ChainUserProvider;
|
|
use Symfony\Component\Security\Core\User\UserInterface;
|
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
|
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\PassportInterface;
|
|
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
|
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
|
|
class JWTAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface
|
|
{
|
|
use ForwardCompatAuthenticatorTrait;
|
|
|
|
/**
|
|
* @var TokenExtractorInterface
|
|
*/
|
|
private $tokenExtractor;
|
|
|
|
/**
|
|
* @var JWTTokenManagerInterface
|
|
*/
|
|
private $jwtManager;
|
|
|
|
/**
|
|
* @var EventDispatcherInterface
|
|
*/
|
|
private $eventDispatcher;
|
|
|
|
/**
|
|
* @var UserProviderInterface
|
|
*/
|
|
private $userProvider;
|
|
|
|
/**
|
|
* @var TranslatorInterface|null
|
|
*/
|
|
private $translator;
|
|
|
|
public function __construct(
|
|
JWTTokenManagerInterface $jwtManager,
|
|
EventDispatcherInterface $eventDispatcher,
|
|
TokenExtractorInterface $tokenExtractor,
|
|
UserProviderInterface $userProvider,
|
|
TranslatorInterface $translator = null
|
|
) {
|
|
$this->tokenExtractor = $tokenExtractor;
|
|
$this->jwtManager = $jwtManager;
|
|
$this->eventDispatcher = $eventDispatcher;
|
|
$this->userProvider = $userProvider;
|
|
$this->translator = $translator;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function start(Request $request, AuthenticationException $authException = null): Response
|
|
{
|
|
$exception = new MissingTokenException('JWT Token not found', 0, $authException);
|
|
$event = new JWTNotFoundEvent($exception, new JWTAuthenticationFailureResponse($exception->getMessageKey()), $request);
|
|
|
|
$this->eventDispatcher->dispatch($event, Events::JWT_NOT_FOUND);
|
|
|
|
return $event->getResponse();
|
|
}
|
|
|
|
public function supports(Request $request): ?bool
|
|
{
|
|
return false !== $this->getTokenExtractor()->extract($request);
|
|
}
|
|
|
|
/**
|
|
* @return Passport
|
|
*/
|
|
public function doAuthenticate(Request $request) /*: Passport */
|
|
{
|
|
$token = $this->getTokenExtractor()->extract($request);
|
|
if ($token === false) {
|
|
throw new \LogicException('Unable to extract a JWT token from the request. Also, make sure to call `supports()` before `authenticate()` to get a proper client error.');
|
|
}
|
|
|
|
try {
|
|
if (!$payload = $this->jwtManager->parse($token)) {
|
|
throw new InvalidTokenException('Invalid JWT Token');
|
|
}
|
|
} catch (JWTDecodeFailureException $e) {
|
|
if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) {
|
|
throw new ExpiredTokenException();
|
|
}
|
|
|
|
throw new InvalidTokenException('Invalid JWT Token', 0, $e);
|
|
}
|
|
|
|
$idClaim = $this->jwtManager->getUserIdClaim();
|
|
if (!isset($payload[$idClaim])) {
|
|
throw new InvalidPayloadException($idClaim);
|
|
}
|
|
|
|
$passport = new SelfValidatingPassport(
|
|
new UserBadge(
|
|
(string)$payload[$idClaim],
|
|
function ($userIdentifier) use ($payload) {
|
|
return $this->loadUser($payload, $userIdentifier);
|
|
}
|
|
)
|
|
);
|
|
|
|
$passport->setAttribute('payload', $payload);
|
|
$passport->setAttribute('token', $token);
|
|
|
|
return $passport;
|
|
}
|
|
|
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
|
{
|
|
$errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData());
|
|
if (null !== $this->translator) {
|
|
$errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security');
|
|
}
|
|
$response = new JWTAuthenticationFailureResponse($errorMessage);
|
|
|
|
if ($exception instanceof ExpiredTokenException) {
|
|
$event = new JWTExpiredEvent($exception, $response, $request);
|
|
$eventName = Events::JWT_EXPIRED;
|
|
} else {
|
|
$event = new JWTInvalidEvent($exception, $response, $request);
|
|
$eventName = Events::JWT_INVALID;
|
|
}
|
|
|
|
$this->eventDispatcher->dispatch($event, $eventName);
|
|
|
|
return $event->getResponse();
|
|
}
|
|
|
|
/**
|
|
* Gets the token extractor to be used for retrieving a JWT token in the
|
|
* current request.
|
|
*
|
|
* Override this method for adding/removing extractors to the chain one or
|
|
* returning a different {@link TokenExtractorInterface} implementation.
|
|
*/
|
|
protected function getTokenExtractor(): TokenExtractorInterface
|
|
{
|
|
return $this->tokenExtractor;
|
|
}
|
|
|
|
/**
|
|
* Gets the jwt manager.
|
|
*/
|
|
protected function getJwtManager(): JWTTokenManagerInterface
|
|
{
|
|
return $this->jwtManager;
|
|
}
|
|
|
|
/**
|
|
* Gets the event dispatcher.
|
|
*/
|
|
protected function getEventDispatcher(): EventDispatcherInterface
|
|
{
|
|
return $this->eventDispatcher;
|
|
}
|
|
|
|
/**
|
|
* Gets the user provider.
|
|
*/
|
|
protected function getUserProvider(): UserProviderInterface
|
|
{
|
|
return $this->userProvider;
|
|
}
|
|
|
|
/**
|
|
* Loads the user to authenticate.
|
|
*
|
|
* @param array $payload The token payload
|
|
* @param string $identity The key from which to retrieve the user "identifier"
|
|
*/
|
|
protected function loadUser(array $payload, string $identity): UserInterface
|
|
{
|
|
if ($this->userProvider instanceof PayloadAwareUserProviderInterface) {
|
|
if (method_exists($this->userProvider, 'loadUserByIdentifierAndPayload')) {
|
|
return $this->userProvider->loadUserByIdentifierAndPayload($identity, $payload);
|
|
} else {
|
|
return $this->userProvider->loadUserByUsernameAndPayload($identity, $payload);
|
|
}
|
|
}
|
|
|
|
if ($this->userProvider instanceof ChainUserProvider) {
|
|
foreach ($this->userProvider->getProviders() as $provider) {
|
|
try {
|
|
if ($provider instanceof PayloadAwareUserProviderInterface) {
|
|
if (method_exists(PayloadAwareUserProviderInterface::class, 'loadUserByIdentifierAndPayload')) {
|
|
return $provider->loadUserByIdentifierAndPayload($identity, $payload);
|
|
} else {
|
|
return $provider->loadUserByUsernameAndPayload($identity, $payload);
|
|
}
|
|
}
|
|
|
|
return $provider->loadUserByIdentifier($identity);
|
|
// More generic call to catch both UsernameNotFoundException for SF<5.3 and new UserNotFoundException
|
|
} catch (AuthenticationException $e) {
|
|
// try next one
|
|
}
|
|
}
|
|
|
|
if (!class_exists(UserNotFoundException::class)) {
|
|
$ex = new UsernameNotFoundException(sprintf('There is no user with username "%s".', $identity));
|
|
$ex->setUsername($identity);
|
|
} else {
|
|
$ex = new UserNotFoundException(sprintf('There is no user with identifier "%s".', $identity));
|
|
$ex->setUserIdentifier($identity);
|
|
}
|
|
|
|
throw $ex;
|
|
}
|
|
|
|
if (method_exists($this->userProvider, 'loadUserByIdentifier')) {
|
|
return $this->userProvider->loadUserByIdentifier($identity);
|
|
} else {
|
|
return $this->userProvider->loadUserByUsername($identity);
|
|
}
|
|
}
|
|
|
|
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
|
|
{
|
|
if (!$passport instanceof Passport) {
|
|
throw new \LogicException(sprintf('Expected "%s" but got "%s".', Passport::class, get_debug_type($passport)));
|
|
}
|
|
|
|
$token = new JWTPostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles(), $passport->getAttribute('token'));
|
|
|
|
$this->eventDispatcher->dispatch(new JWTAuthenticatedEvent($passport->getAttribute('payload'), $token), Events::JWT_AUTHENTICATED);
|
|
|
|
return $token;
|
|
}
|
|
|
|
public function createToken(Passport $passport, string $firewallName): TokenInterface
|
|
{
|
|
$token = new JWTPostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles(), $passport->getAttribute('token'));
|
|
|
|
$this->eventDispatcher->dispatch(new JWTAuthenticatedEvent($passport->getAttribute('payload'), $token), Events::JWT_AUTHENTICATED);
|
|
|
|
return $token;
|
|
}
|
|
}
|