-
-
Notifications
You must be signed in to change notification settings - Fork 614
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Symfony 5.x new Security Authenticator
- Loading branch information
1 parent
0d36113
commit a4d8015
Showing
9 changed files
with
572 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator; | ||
|
||
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\Authentication\Token\PreAuthenticationJWTUserToken; | ||
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\PassportInterface; | ||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; | ||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; | ||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; | ||
|
||
class JWTTokenAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface | ||
{ | ||
/** | ||
* @var TokenExtractorInterface | ||
*/ | ||
private $tokenExtractor; | ||
|
||
/** | ||
* @var JWTTokenManagerInterface | ||
*/ | ||
private $jwtManager; | ||
|
||
/** | ||
* @var EventDispatcherInterface | ||
*/ | ||
private $eventDispatcher; | ||
|
||
/** | ||
* @var UserProviderInterface | ||
*/ | ||
private $userProvider; | ||
|
||
public function __construct( | ||
JWTTokenManagerInterface $jwtManager, | ||
EventDispatcherInterface $eventDispatcher, | ||
TokenExtractorInterface $tokenExtractor, | ||
UserProviderInterface $userProvider | ||
) { | ||
$this->tokenExtractor = $tokenExtractor; | ||
$this->jwtManager = $jwtManager; | ||
$this->eventDispatcher = $eventDispatcher; | ||
$this->userProvider = $userProvider; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function start(Request $request, AuthenticationException $authException = null): JWTAuthenticationFailureResponse | ||
{ | ||
$exception = new MissingTokenException('JWT Token not found', 0, $authException); | ||
$event = new JWTNotFoundEvent($exception, new JWTAuthenticationFailureResponse($exception->getMessageKey())); | ||
|
||
$this->eventDispatcher->dispatch($event, Events::JWT_NOT_FOUND); | ||
|
||
return $event->getResponse(); | ||
} | ||
|
||
public function supports(Request $request): ?bool | ||
{ | ||
return false !== $this->getTokenExtractor()->extract($request); | ||
} | ||
|
||
public function authenticate(Request $request): PassportInterface | ||
{ | ||
$tokenExtractor = $this->getTokenExtractor(); | ||
|
||
try { | ||
if (!$payload = $this->jwtManager->decodeFromJsonWebToken($tokenExtractor->extract($request))) { | ||
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); | ||
} | ||
|
||
return new SelfValidatingPassport( | ||
new UserBadge($payload[$idClaim], | ||
function ($userIdentifier) use($payload) { | ||
return $this->loadUser($payload, $userIdentifier); | ||
}) | ||
); | ||
} | ||
|
||
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()); | ||
$response = new JWTAuthenticationFailureResponse($errorMessage); | ||
|
||
if ($exception instanceof ExpiredTokenException) { | ||
$event = new JWTExpiredEvent($exception, $response); | ||
$eventName = Events::JWT_EXPIRED; | ||
} else { | ||
$event = new JWTInvalidEvent($exception, $response); | ||
$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; | ||
} | ||
|
||
/** | ||
* 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(PayloadAwareUserProviderInterface::class, '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 $this->userProvider->loadUserByIdentifierAndPayload($identity, $payload); | ||
} else { | ||
return $this->userProvider->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(UsernameNotFoundException::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(UserProviderInterface::class, 'loadUserByIdentifier')) { | ||
return $this->userProvider->loadUserByIdentifierAndPayload($identity); | ||
} else { | ||
return $this->userProvider->loadUserByUsernameAndPayload($identity); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
why?