welcome back to dyb-tech
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider;
|
||||
|
||||
@trigger_error(sprintf('The "%s" class is deprecated since version 2.5 and will be removed in 3.0. Use "%s" or create your own "%s" implementation instead.', DefaultJWSProvider::class, LcobucciJWSProvider::class, JWSProviderInterface::class), E_USER_DEPRECATED);
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\KeyLoaderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\CreatedJWS;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\LoadedJWS;
|
||||
use Namshi\JOSE\JWS;
|
||||
|
||||
/**
|
||||
* JWS Provider, Namshi\JOSE library integration.
|
||||
* Supports OpenSSL and phpseclib crypto engines.
|
||||
*
|
||||
* @final
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @deprecated since version 2.5, to be removed in 3.0
|
||||
*/
|
||||
class DefaultJWSProvider implements JWSProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var KeyLoaderInterface
|
||||
*/
|
||||
private $keyLoader;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cryptoEngine;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $signatureAlgorithm;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $ttl;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $clockSkew;
|
||||
|
||||
/**
|
||||
* @param string $cryptoEngine
|
||||
* @param string $signatureAlgorithm
|
||||
* @param int $ttl
|
||||
* @param int $clockSkew
|
||||
*
|
||||
* @throws \InvalidArgumentException If the given algorithm is not supported
|
||||
*/
|
||||
public function __construct(KeyLoaderInterface $keyLoader, $cryptoEngine, $signatureAlgorithm, $ttl, $clockSkew)
|
||||
{
|
||||
if (null !== $ttl && !is_numeric($ttl)) {
|
||||
throw new \InvalidArgumentException(sprintf('The TTL should be a numeric value, got %s instead.', $ttl));
|
||||
}
|
||||
|
||||
if (null !== $clockSkew && !is_numeric($clockSkew)) {
|
||||
throw new \InvalidArgumentException(sprintf('The clock skew should be a numeric value, got %s instead.', $clockSkew));
|
||||
}
|
||||
|
||||
$cryptoEngine = 'openssl' == $cryptoEngine ? 'OpenSSL' : 'SecLib';
|
||||
|
||||
if (!$this->isAlgorithmSupportedForEngine($cryptoEngine, $signatureAlgorithm)) {
|
||||
throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported for %s', $signatureAlgorithm, $cryptoEngine));
|
||||
}
|
||||
|
||||
$this->keyLoader = $keyLoader;
|
||||
$this->cryptoEngine = $cryptoEngine;
|
||||
$this->signatureAlgorithm = $signatureAlgorithm;
|
||||
$this->ttl = $ttl;
|
||||
$this->clockSkew = $clockSkew;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(array $payload, array $header = [])
|
||||
{
|
||||
$header['alg'] = $this->signatureAlgorithm;
|
||||
|
||||
$jws = new JWS($header, $this->cryptoEngine);
|
||||
$claims = ['iat' => time()];
|
||||
|
||||
if (null !== $this->ttl && !isset($payload['exp'])) {
|
||||
$claims['exp'] = time() + $this->ttl;
|
||||
}
|
||||
|
||||
$jws->setPayload($payload + $claims);
|
||||
$jws->sign(
|
||||
$this->keyLoader->loadKey('private'),
|
||||
$this->keyLoader->getPassphrase()
|
||||
);
|
||||
|
||||
return new CreatedJWS($jws->getTokenString(), $jws->isSigned());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($token)
|
||||
{
|
||||
$jws = JWS::load($token, false, null, $this->cryptoEngine);
|
||||
|
||||
return new LoadedJWS(
|
||||
$jws->getPayload(),
|
||||
$jws->verify($this->keyLoader->loadKey('public'), $this->signatureAlgorithm),
|
||||
null !== $this->ttl,
|
||||
$jws->getHeader(),
|
||||
$this->clockSkew
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cryptoEngine
|
||||
* @param string $signatureAlgorithm
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isAlgorithmSupportedForEngine($cryptoEngine, $signatureAlgorithm)
|
||||
{
|
||||
$signerClass = sprintf('Namshi\\JOSE\\Signer\\%s\\%s', $cryptoEngine, $signatureAlgorithm);
|
||||
|
||||
return class_exists($signerClass);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\CreatedJWS;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\LoadedJWS;
|
||||
|
||||
/**
|
||||
* Interface for classes that are able to create and load JSON web signatures (JWS).
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
interface JWSProviderInterface
|
||||
{
|
||||
/**
|
||||
* Creates a new JWS signature from a given payload.
|
||||
*
|
||||
* @return CreatedJWS
|
||||
*/
|
||||
public function create(array $payload, array $header = []);
|
||||
|
||||
/**
|
||||
* Loads an existing JWS signature from a given JWT token.
|
||||
*
|
||||
* @param string $token
|
||||
*
|
||||
* @return LoadedJWS
|
||||
*/
|
||||
public function load($token);
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider;
|
||||
|
||||
use Lcobucci\Clock\Clock;
|
||||
use Lcobucci\Clock\SystemClock;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use Lcobucci\JWT\Encoding\ChainedFormatter;
|
||||
use Lcobucci\JWT\Encoding\JoseEncoder;
|
||||
use Lcobucci\JWT\Parser;
|
||||
use Lcobucci\JWT\Signer;
|
||||
use Lcobucci\JWT\Signer\Ecdsa;
|
||||
use Lcobucci\JWT\Signer\Hmac;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha384;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha512;
|
||||
use Lcobucci\JWT\Signer\Key;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\Token\Builder as JWTBuilder;
|
||||
use Lcobucci\JWT\Token\Parser as JWTParser;
|
||||
use Lcobucci\JWT\Token\Plain;
|
||||
use Lcobucci\JWT\Token\RegisteredClaims;
|
||||
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
|
||||
use Lcobucci\JWT\Validation\Constraint\SignedWith;
|
||||
use Lcobucci\JWT\Validation\Constraint\ValidAt;
|
||||
use Lcobucci\JWT\Validation\Validator;
|
||||
use Lcobucci\JWT\ValidationData;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\KeyLoaderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\RawKeyLoader;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\CreatedJWS;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\LoadedJWS;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class LcobucciJWSProvider implements JWSProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var KeyLoaderInterface
|
||||
*/
|
||||
private $keyLoader;
|
||||
|
||||
/**
|
||||
* @var Clock
|
||||
*/
|
||||
private $clock;
|
||||
|
||||
/**
|
||||
* @var Signer
|
||||
*/
|
||||
private $signer;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $ttl;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $clockSkew;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $allowNoExpiration;
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException If the given crypto engine is not supported
|
||||
*/
|
||||
public function __construct(KeyLoaderInterface $keyLoader, string $cryptoEngine, string $signatureAlgorithm, ?int $ttl, ?int $clockSkew, bool $allowNoExpiration = false, Clock $clock = null)
|
||||
{
|
||||
if ('openssl' !== $cryptoEngine) {
|
||||
throw new \InvalidArgumentException(sprintf('The %s provider supports only "openssl" as crypto engine.', self::class));
|
||||
}
|
||||
if (null === $clock) {
|
||||
$clock = new SystemClock(new \DateTimeZone('UTC'));
|
||||
}
|
||||
|
||||
$this->keyLoader = $keyLoader;
|
||||
$this->clock = $clock;
|
||||
$this->signer = $this->getSignerForAlgorithm($signatureAlgorithm);
|
||||
$this->ttl = $ttl;
|
||||
$this->clockSkew = $clockSkew;
|
||||
$this->allowNoExpiration = $allowNoExpiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(array $payload, array $header = [])
|
||||
{
|
||||
if (class_exists(JWTBuilder::class)) {
|
||||
$jws = new JWTBuilder(new JoseEncoder(), ChainedFormatter::default());
|
||||
} else {
|
||||
$jws = new Builder();
|
||||
}
|
||||
|
||||
foreach ($header as $k => $v) {
|
||||
$jws = $jws->withHeader($k, $v);
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
$issuedAt = $payload['iat'] ?? $now;
|
||||
unset($payload['iat']);
|
||||
|
||||
$jws = $jws->issuedAt(!$issuedAt instanceof \DateTimeImmutable ? new \DateTimeImmutable("@{$issuedAt}") : $issuedAt);
|
||||
|
||||
if (null !== $this->ttl || isset($payload['exp'])) {
|
||||
$exp = $payload['exp'] ?? $now + $this->ttl;
|
||||
unset($payload['exp']);
|
||||
|
||||
if ($exp) {
|
||||
$jws = $jws->expiresAt(!$exp instanceof \DateTimeImmutable ? new \DateTimeImmutable("@{$exp}") : $exp);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($payload['sub'])) {
|
||||
$jws = $jws->relatedTo($payload['sub']);
|
||||
unset($payload['sub']);
|
||||
}
|
||||
|
||||
if (interface_exists(RegisteredClaims::class)) {
|
||||
$jws = $this->addStandardClaims($jws, $payload);
|
||||
}
|
||||
|
||||
foreach ($payload as $name => $value) {
|
||||
$jws = $jws->withClaim($name, $value);
|
||||
}
|
||||
|
||||
$e = $token = null;
|
||||
try {
|
||||
$token = $this->getSignedToken($jws);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
|
||||
return new CreatedJWS((string) $token, null === $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($token)
|
||||
{
|
||||
if (class_exists(JWTParser::class)) {
|
||||
$jws = (new JWTParser(new JoseEncoder()))->parse((string) $token);
|
||||
} else {
|
||||
$jws = (new Parser())->parse((string) $token);
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
foreach ($jws->claims()->all() as $name => $value) {
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
$value = $value->getTimestamp();
|
||||
}
|
||||
$payload[$name] = $value;
|
||||
}
|
||||
|
||||
$jws = new LoadedJWS(
|
||||
$payload,
|
||||
$this->verify($jws),
|
||||
false == $this->allowNoExpiration,
|
||||
$jws->headers()->all(),
|
||||
$this->clockSkew
|
||||
);
|
||||
|
||||
return $jws;
|
||||
}
|
||||
|
||||
private function getSignerForAlgorithm($signatureAlgorithm)
|
||||
{
|
||||
$signerMap = [
|
||||
'HS256' => Sha256::class,
|
||||
'HS384' => Sha384::class,
|
||||
'HS512' => Sha512::class,
|
||||
'RS256' => Signer\Rsa\Sha256::class,
|
||||
'RS384' => Signer\Rsa\Sha384::class,
|
||||
'RS512' => Signer\Rsa\Sha512::class,
|
||||
'ES256' => Signer\Ecdsa\Sha256::class,
|
||||
'ES384' => Signer\Ecdsa\Sha384::class,
|
||||
'ES512' => Signer\Ecdsa\Sha512::class,
|
||||
];
|
||||
|
||||
if (!isset($signerMap[$signatureAlgorithm])) {
|
||||
throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported by %s', $signatureAlgorithm, self::class));
|
||||
}
|
||||
|
||||
$signerClass = $signerMap[$signatureAlgorithm];
|
||||
|
||||
if (is_subclass_of($signerClass, Ecdsa::class) && method_exists($signerClass, 'create')) {
|
||||
return $signerClass::create();
|
||||
}
|
||||
|
||||
return new $signerClass();
|
||||
}
|
||||
|
||||
private function getSignedToken(Builder $jws)
|
||||
{
|
||||
if (class_exists(InMemory::class)) {
|
||||
$key = InMemory::plainText($this->keyLoader->loadKey(RawKeyLoader::TYPE_PRIVATE), $this->signer instanceof Hmac ? '' : (string) $this->keyLoader->getPassphrase());
|
||||
} else {
|
||||
$key = new Key($this->keyLoader->loadKey(RawKeyLoader::TYPE_PRIVATE), $this->signer instanceof Hmac ? '' : (string) $this->keyLoader->getPassphrase());
|
||||
}
|
||||
|
||||
$token = $jws->getToken($this->signer, $key);
|
||||
|
||||
if (!$token instanceof Plain) {
|
||||
return (string) $token;
|
||||
}
|
||||
|
||||
return $token->toString();
|
||||
}
|
||||
|
||||
private function verify(Token $jwt)
|
||||
{
|
||||
if (class_exists(InMemory::class)) {
|
||||
$key = InMemory::plainText($this->signer instanceof Hmac ? $this->keyLoader->loadKey(RawKeyLoader::TYPE_PRIVATE) : $this->keyLoader->loadKey(RawKeyLoader::TYPE_PUBLIC));
|
||||
} else {
|
||||
$key = new Key($this->signer instanceof Hmac ? $this->keyLoader->loadKey(RawKeyLoader::TYPE_PRIVATE) : $this->keyLoader->loadKey(RawKeyLoader::TYPE_PUBLIC));
|
||||
}
|
||||
|
||||
$validator = new Validator();
|
||||
$classValidator = class_exists(LooseValidAt::class) ? LooseValidAt::class : ValidAt::class;
|
||||
|
||||
$isValid = $validator->validate(
|
||||
$jwt,
|
||||
new $classValidator($this->clock, new \DateInterval("PT{$this->clockSkew}S")),
|
||||
new SignedWith($this->signer, $key)
|
||||
);
|
||||
|
||||
$publicKeys = $this->keyLoader->getAdditionalPublicKeys();
|
||||
if ($isValid || $this->signer instanceof Hmac || empty($publicKeys)) {
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
// If the key used to verify the token is invalid, and it's not Hmac algorithm, try with additional public keys
|
||||
foreach ($publicKeys as $key) {
|
||||
$isValid = $validator->validate(
|
||||
$jwt,
|
||||
new $classValidator($this->clock, new \DateInterval("PT{$this->clockSkew}S")),
|
||||
new SignedWith($this->signer, InMemory::plainText($key))
|
||||
);
|
||||
|
||||
if ($isValid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function addStandardClaims(Builder $builder, array &$payload): Builder
|
||||
{
|
||||
$mutatorMap = [
|
||||
RegisteredClaims::AUDIENCE => 'permittedFor',
|
||||
RegisteredClaims::ID => 'identifiedBy',
|
||||
RegisteredClaims::ISSUER => 'issuedBy',
|
||||
RegisteredClaims::NOT_BEFORE => 'canOnlyBeUsedAfter',
|
||||
];
|
||||
|
||||
foreach ($payload as $claim => $value) {
|
||||
if (!isset($mutatorMap[$claim])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mutator = $mutatorMap[$claim];
|
||||
unset($payload[$claim]);
|
||||
|
||||
if (\is_array($value)) {
|
||||
$builder = \call_user_func_array([$builder, $mutator], $value);
|
||||
continue;
|
||||
}
|
||||
|
||||
$builder = $builder->{$mutator}($value);
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\HeaderAwareJWTEncoderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTEncodedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\InMemoryUser;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Provides convenient methods to manage JWT creation/verification.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTManager implements JWTManagerInterface, JWTTokenManagerInterface
|
||||
{
|
||||
/**
|
||||
* @var JWTEncoderInterface
|
||||
*/
|
||||
protected $jwtEncoder;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @deprecated since v2.15
|
||||
*/
|
||||
protected $userIdentityField;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $userIdClaim;
|
||||
|
||||
/**
|
||||
* @param string|null $userIdClaim
|
||||
*/
|
||||
public function __construct(JWTEncoderInterface $encoder, EventDispatcherInterface $dispatcher, $userIdClaim = null)
|
||||
{
|
||||
$this->jwtEncoder = $encoder;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->userIdentityField = 'username';
|
||||
$this->userIdClaim = $userIdClaim;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
public function create(UserInterface $user): string
|
||||
{
|
||||
$payload = ['roles' => $user->getRoles()];
|
||||
$this->addUserIdentityToPayload($user, $payload);
|
||||
|
||||
return $this->generateJwtStringAndDispatchEvents($user, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
public function createFromPayload(UserInterface $user, array $payload): string
|
||||
{
|
||||
$payload = array_merge(['roles' => $user->getRoles()], $payload);
|
||||
$this->addUserIdentityToPayload($user, $payload);
|
||||
|
||||
return $this->generateJwtStringAndDispatchEvents($user, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
private function generateJwtStringAndDispatchEvents(UserInterface $user, array $payload): string
|
||||
{
|
||||
$jwtCreatedEvent = new JWTCreatedEvent($payload, $user);
|
||||
$this->dispatcher->dispatch($jwtCreatedEvent, Events::JWT_CREATED);
|
||||
|
||||
if ($this->jwtEncoder instanceof HeaderAwareJWTEncoderInterface) {
|
||||
$jwtString = $this->jwtEncoder->encode($jwtCreatedEvent->getData(), $jwtCreatedEvent->getHeader());
|
||||
} else {
|
||||
$jwtString = $this->jwtEncoder->encode($jwtCreatedEvent->getData());
|
||||
}
|
||||
|
||||
$jwtEncodedEvent = new JWTEncodedEvent($jwtString);
|
||||
|
||||
$this->dispatcher->dispatch($jwtEncodedEvent, Events::JWT_ENCODED);
|
||||
|
||||
return $jwtString;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws JWTDecodeFailureException
|
||||
*/
|
||||
public function decode(TokenInterface $token)
|
||||
{
|
||||
if (!($payload = $this->jwtEncoder->decode($token->getCredentials()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$event = new JWTDecodedEvent($payload);
|
||||
$this->dispatcher->dispatch($event, Events::JWT_DECODED);
|
||||
|
||||
if (!$event->isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $event->getPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(string $jwtToken): array
|
||||
{
|
||||
$payload = $this->jwtEncoder->decode($jwtToken);
|
||||
|
||||
$event = new JWTDecodedEvent($payload);
|
||||
$this->dispatcher->dispatch($event, Events::JWT_DECODED);
|
||||
|
||||
if (!$event->isValid()) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'The token was marked as invalid by an event listener after successful decoding.');
|
||||
}
|
||||
|
||||
return $event->getPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user identity to payload, username by default.
|
||||
* Override this if you need to identify it by another property.
|
||||
*
|
||||
* @param array &$payload
|
||||
*/
|
||||
protected function addUserIdentityToPayload(UserInterface $user, array &$payload)
|
||||
{
|
||||
$accessor = PropertyAccess::createPropertyAccessor();
|
||||
$identityField = $this->userIdClaim ?: $this->userIdentityField;
|
||||
|
||||
if ($user instanceof InMemoryUser && 'username' === $identityField) {
|
||||
$payload[$identityField] = $accessor->getValue($user, 'userIdentifier');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$payload[$identityField] = $accessor->getValue($user, $accessor->isReadable($user, $identityField) ? $identityField : 'user_identifier');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserIdentityField(): string
|
||||
{
|
||||
if (0 === func_num_args() || func_get_arg(0)) {
|
||||
trigger_deprecation('lexik/jwt-authentication-bundle', '2.15', 'The "%s()" method is deprecated.', __METHOD__);
|
||||
}
|
||||
|
||||
return $this->userIdentityField;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUserIdentityField($field)
|
||||
{
|
||||
if (1 >= func_num_args() || func_get_arg(1)) {
|
||||
trigger_deprecation('lexik/jwt-authentication-bundle', '2.15', 'The "%s()" method is deprecated.', __METHOD__);
|
||||
}
|
||||
|
||||
$this->userIdentityField = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserIdClaim(): ?string
|
||||
{
|
||||
return $this->userIdClaim;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* JWTManagerInterface.
|
||||
*
|
||||
* @deprecated since 2.0, removed in 3.0. Use {@link JWTTokenManagerInterface} instead
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
interface JWTManagerInterface
|
||||
{
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
public function create(UserInterface $user);
|
||||
|
||||
/**
|
||||
* @return array|false The JWT token payload or false if an error occurs
|
||||
*/
|
||||
public function decode(TokenInterface $token);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* JWTTokenManagerInterface must be implemented by classes able to create/decode
|
||||
* JWT tokens.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
* @author Eric Lannez <eric.lannez@gmail.com>
|
||||
*
|
||||
* @method string createFromPayload(UserInterface $user, array $payload = []);
|
||||
* @method array parse(string $token) Parses a raw JWT token and returns its payload
|
||||
*/
|
||||
interface JWTTokenManagerInterface
|
||||
{
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
public function create(UserInterface $user);
|
||||
|
||||
/**
|
||||
* @return array|false The JWT token payload or false if an error occurs
|
||||
* @throws JWTDecodeFailureException
|
||||
*/
|
||||
public function decode(TokenInterface $token);
|
||||
|
||||
/**
|
||||
* Sets the field used as identifier to load an user from a JWT payload.
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @deprecated since 2.15, use {@see UserInterface::getUserIdentifier()} instead
|
||||
*/
|
||||
public function setUserIdentityField($field);
|
||||
|
||||
/**
|
||||
* Returns the field used as identifier to load an user from a JWT payload.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated since 2.15, use {@see UserInterface::getUserIdentifier()} instead
|
||||
*/
|
||||
public function getUserIdentityField();
|
||||
|
||||
/**
|
||||
* Returns the claim used as identifier to load an user from a JWT payload.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserIdClaim();
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
/**
|
||||
* Abstract class for key loaders.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractKeyLoader implements KeyLoaderInterface
|
||||
{
|
||||
private $signingKey;
|
||||
private $publicKey;
|
||||
private $passphrase;
|
||||
private $additionalPublicKeys;
|
||||
|
||||
public function __construct(?string $signingKey = null, ?string $publicKey = null, ?string $passphrase = null, array $additionalPublicKeys = [])
|
||||
{
|
||||
$this->signingKey = $signingKey;
|
||||
$this->publicKey = $publicKey;
|
||||
$this->passphrase = $passphrase;
|
||||
$this->additionalPublicKeys = $additionalPublicKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPassphrase()
|
||||
{
|
||||
return $this->passphrase;
|
||||
}
|
||||
|
||||
public function getSigningKey()
|
||||
{
|
||||
return $this->signingKey && is_file($this->signingKey) ? $this->readKey(self::TYPE_PRIVATE) : $this->signingKey;
|
||||
}
|
||||
|
||||
public function getPublicKey()
|
||||
{
|
||||
return $this->publicKey && is_file($this->publicKey) ? $this->readKey(self::TYPE_PUBLIC) : $this->publicKey;
|
||||
}
|
||||
|
||||
public function getAdditionalPublicKeys(): array
|
||||
{
|
||||
$contents = [];
|
||||
|
||||
foreach ($this->additionalPublicKeys as $key) {
|
||||
if (!$key || !is_file($key) || !is_readable($key)) {
|
||||
throw new \RuntimeException(sprintf('Additional public key "%s" does not exist or is not readable. Did you correctly set the "lexik_jwt_authentication.additional_public_keys" configuration key?', $key));
|
||||
}
|
||||
|
||||
$rawKey = $key;
|
||||
|
||||
if (is_file($key)) {
|
||||
$rawKey = @file_get_contents($key);
|
||||
|
||||
if (false === $rawKey) {
|
||||
// Try invalidating the realpath cache
|
||||
clearstatcache(true, $key);
|
||||
$rawKey = file_get_contents($key);
|
||||
}
|
||||
}
|
||||
$contents[] = $rawKey;
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type One of "public" or "private"
|
||||
*
|
||||
* @return string The path of the key, an empty string if not a valid path
|
||||
*
|
||||
* @throws \InvalidArgumentException If the given type is not valid
|
||||
* @throws \InvalidArgumentException If the given type is not valid
|
||||
*/
|
||||
protected function getKeyPath($type)
|
||||
{
|
||||
if (!in_array($type, [self::TYPE_PUBLIC, self::TYPE_PRIVATE])) {
|
||||
throw new \InvalidArgumentException(sprintf('The key type must be "public" or "private", "%s" given.', $type));
|
||||
}
|
||||
|
||||
$path = self::TYPE_PUBLIC === $type ? $this->publicKey : $this->signingKey;
|
||||
|
||||
if (!is_file($path) || !is_readable($path)) {
|
||||
throw new \RuntimeException(sprintf('%s key is not a file or is not readable.', ucfirst($type)));
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function readKey($type)
|
||||
{
|
||||
$isPublic = self::TYPE_PUBLIC === $type;
|
||||
$key = $isPublic ? $this->publicKey : $this->signingKey;
|
||||
|
||||
if (!$key || !is_file($key) || !is_readable($key)) {
|
||||
if ($isPublic) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('Signature key "%s" does not exist or is not readable. Did you correctly set the "lexik_jwt_authentication.signature_key" configuration key?', $key));
|
||||
}
|
||||
|
||||
$rawKey = @file_get_contents($key);
|
||||
|
||||
if (false === $rawKey) {
|
||||
// Try invalidating the realpath cache
|
||||
clearstatcache(true, $key);
|
||||
$rawKey = file_get_contents($key);
|
||||
}
|
||||
|
||||
return $rawKey;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
interface KeyDumperInterface
|
||||
{
|
||||
/**
|
||||
* Dumps a key to be shared between parties.
|
||||
*
|
||||
* @return resource|string
|
||||
*/
|
||||
public function dumpKey();
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
/**
|
||||
* Interface for classes that are able to load crypto keys.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @method string|null getPublicKey()
|
||||
* @method string|null getSigningKey()
|
||||
* @method array getAdditionalPublicKeys()
|
||||
*/
|
||||
interface KeyLoaderInterface
|
||||
{
|
||||
public const TYPE_PUBLIC = 'public';
|
||||
|
||||
public const TYPE_PRIVATE = 'private';
|
||||
|
||||
/**
|
||||
* Loads a key from a given type (public or private).
|
||||
*
|
||||
* @param resource|string|null $type
|
||||
*
|
||||
* @return resource|string|null
|
||||
*/
|
||||
public function loadKey($type);
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPassphrase();
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
@trigger_error(sprintf('The "%s\OpenSSLKeyLoader" class is deprecated since version 2.5 and will be removed in 3.0. Use "%s" instead.', __NAMESPACE__, RawKeyLoader::class), E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Load crypto keys for the OpenSSL crypto engine.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @deprecated since version 2.5, to be removed in 3.0. Use RawKeyLoader instead
|
||||
*/
|
||||
class OpenSSLKeyLoader extends AbstractKeyLoader implements KeyDumperInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \RuntimeException If the key cannot be read
|
||||
* @throws \RuntimeException Either the key or the passphrase is not valid
|
||||
*/
|
||||
public function loadKey($type)
|
||||
{
|
||||
if (!in_array($type, [self::TYPE_PUBLIC, self::TYPE_PRIVATE])) {
|
||||
throw new \InvalidArgumentException(sprintf('The key type must be "public" or "private", "%s" given.', $type));
|
||||
}
|
||||
|
||||
$keyPath = $this->getKeyPath($type);
|
||||
$rawKey = @file_get_contents($keyPath);
|
||||
|
||||
if (false === $rawKey) {
|
||||
// Try invalidating the realpath cache
|
||||
clearstatcache(true, $keyPath);
|
||||
$rawKey = file_get_contents($keyPath);
|
||||
}
|
||||
|
||||
$key = call_user_func_array("openssl_pkey_get_$type", self::TYPE_PRIVATE == $type ? [$rawKey, $this->getPassphrase()] : [$rawKey]);
|
||||
|
||||
if (!$key) {
|
||||
$sslError = '';
|
||||
while ($msg = trim(openssl_error_string(), " \n\r\t\0\x0B\"")) {
|
||||
if ('error:' === substr($msg, 0, 6)) {
|
||||
$msg = substr($msg, 6);
|
||||
}
|
||||
$sslError .= "\n $msg";
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('Failed to load %s key: %s', $type, $sslError));
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dumpKey()
|
||||
{
|
||||
$key = openssl_pkey_get_details($this->loadKey('public'));
|
||||
|
||||
if (!isset($key['key'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $key['key'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
/**
|
||||
* Reads crypto keys.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class RawKeyLoader extends AbstractKeyLoader implements KeyDumperInterface
|
||||
{
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \RuntimeException If the key cannot be read
|
||||
*/
|
||||
public function loadKey($type)
|
||||
{
|
||||
if (!in_array($type, [self::TYPE_PUBLIC, self::TYPE_PRIVATE])) {
|
||||
throw new \InvalidArgumentException(sprintf('The key type must be "public" or "private", "%s" given.', $type));
|
||||
}
|
||||
|
||||
if (self::TYPE_PUBLIC === $type) {
|
||||
return $this->dumpKey();
|
||||
}
|
||||
|
||||
return $this->getSigningKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dumpKey()
|
||||
{
|
||||
if ($publicKey = $this->getPublicKey()) {
|
||||
return $publicKey;
|
||||
}
|
||||
|
||||
$signingKey = $this->getSigningKey();
|
||||
|
||||
// no public key provided, compute it from signing key
|
||||
try {
|
||||
$publicKey = openssl_pkey_get_details(openssl_pkey_get_private($signingKey, $this->getPassphrase()))['key'];
|
||||
} catch (\Throwable $e) {
|
||||
throw new \RuntimeException('Secret key either does not exist, is not readable or is invalid. Did you correctly set the "lexik_jwt_authentication.secret_key" config option?');
|
||||
}
|
||||
|
||||
return $publicKey;
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken;
|
||||
|
||||
use Jose\Bundle\JoseFramework\Services\JWEBuilder;
|
||||
use Jose\Bundle\JoseFramework\Services\JWEBuilderFactory;
|
||||
use Jose\Bundle\JoseFramework\Services\JWSBuilder;
|
||||
use Jose\Bundle\JoseFramework\Services\JWSBuilderFactory;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Encryption\Serializer\CompactSerializer as JweCompactSerializer;
|
||||
use Jose\Component\Signature\Serializer\CompactSerializer as JwsCompactSerializer;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\BeforeJWEComputationEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
final class AccessTokenBuilder
|
||||
{
|
||||
/**
|
||||
* @var JWSBuilder
|
||||
*/
|
||||
private $jwsBuilder;
|
||||
|
||||
/**
|
||||
* @var null|JWEBuilder
|
||||
*/
|
||||
private $jweBuilder = null;
|
||||
|
||||
/**
|
||||
* @var JWK
|
||||
*/
|
||||
private $signatureKey;
|
||||
|
||||
/**
|
||||
* @var JWK|null
|
||||
*/
|
||||
private $encryptionKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $signatureAlgorithm;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $keyEncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $contentEncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
private $dispatcher;
|
||||
|
||||
public function __construct(
|
||||
EventDispatcherInterface $dispatcher,
|
||||
JWSBuilderFactory $jwsBuilderFactory,
|
||||
?JWEBuilderFactory $jweBuilderFactory,
|
||||
string $signatureAlgorithm,
|
||||
string $signatureKey,
|
||||
?string $keyEncryptionAlgorithm,
|
||||
?string $contentEncryptionAlgorithm,
|
||||
?string $encryptionKey
|
||||
) {
|
||||
$this->jwsBuilder = $jwsBuilderFactory->create([$signatureAlgorithm]);
|
||||
if ($jweBuilderFactory !== null && $keyEncryptionAlgorithm !== null && $contentEncryptionAlgorithm !== null) {
|
||||
$this->jweBuilder = $jweBuilderFactory->create([$keyEncryptionAlgorithm], [$contentEncryptionAlgorithm], []);
|
||||
}
|
||||
$this->signatureKey = JWK::createFromJson($signatureKey);
|
||||
$this->encryptionKey = $encryptionKey ? JWK::createFromJson($encryptionKey) : null;
|
||||
$this->signatureAlgorithm = $signatureAlgorithm;
|
||||
$this->keyEncryptionAlgorithm = $keyEncryptionAlgorithm;
|
||||
$this->contentEncryptionAlgorithm = $contentEncryptionAlgorithm;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
public function build(array $header, array $claims): string
|
||||
{
|
||||
$token = $this->buildJWS($header, $claims);
|
||||
|
||||
if ($this->jweBuilder !== null) {
|
||||
$token = $this->buildJWE($claims, $token);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $header
|
||||
* @param array<string, mixed> $claims
|
||||
*/
|
||||
private function buildJWS(array $header, array $claims): string
|
||||
{
|
||||
$header['alg'] = $this->signatureAlgorithm;
|
||||
if ($this->signatureKey->has('kid')) {
|
||||
$header['kid'] = $this->signatureKey->get('kid');
|
||||
}
|
||||
$jws = $this->jwsBuilder
|
||||
->create()
|
||||
->withPayload(json_encode($claims, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE))
|
||||
->addSignature($this->signatureKey, $header)
|
||||
->build()
|
||||
;
|
||||
$token = (new JwsCompactSerializer())->serialize($jws);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $header
|
||||
*/
|
||||
private function buildJWE(array $claims, string $payload): string
|
||||
{
|
||||
$header = [
|
||||
'alg' => $this->keyEncryptionAlgorithm,
|
||||
'enc' => $this->contentEncryptionAlgorithm,
|
||||
'cty' => 'JWT',
|
||||
'typ' => 'JWT',
|
||||
];
|
||||
if ($this->encryptionKey->has('kid')) {
|
||||
$header['kid'] = $this->encryptionKey->get('kid');
|
||||
}
|
||||
foreach (['exp', 'iat', 'nbf'] as $claim) {
|
||||
if (array_key_exists($claim, $claims)) {
|
||||
$header[$claim] = $claims[$claim];
|
||||
}
|
||||
}
|
||||
$event = $this->dispatcher->dispatch(new BeforeJWEComputationEvent($header), Events::BEFORE_JWE_COMPUTATION);
|
||||
$jwe = $this->jweBuilder
|
||||
->create()
|
||||
->withPayload($payload)
|
||||
->withSharedProtectedHeader($event->getHeader())
|
||||
->addRecipient($this->encryptionKey)
|
||||
->build()
|
||||
;
|
||||
return (new JweCompactSerializer())->serialize($jwe);
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken;
|
||||
|
||||
use Jose\Bundle\JoseFramework\Services\ClaimCheckerManager;
|
||||
use Jose\Bundle\JoseFramework\Services\ClaimCheckerManagerFactory;
|
||||
use Jose\Bundle\JoseFramework\Services\HeaderCheckerManager;
|
||||
use Jose\Bundle\JoseFramework\Services\JWELoader;
|
||||
use Jose\Bundle\JoseFramework\Services\JWELoaderFactory;
|
||||
use Jose\Bundle\JoseFramework\Services\JWSLoader;
|
||||
use Jose\Bundle\JoseFramework\Services\JWSLoaderFactory;
|
||||
use Jose\Component\Checker\InvalidClaimException;
|
||||
use Jose\Component\Checker\MissingMandatoryClaimException;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
|
||||
final class AccessTokenLoader
|
||||
{
|
||||
private $jwsLoader;
|
||||
|
||||
private $jwsHeaderCheckerManager;
|
||||
|
||||
private $claimCheckerManager;
|
||||
|
||||
private $jweLoader;
|
||||
|
||||
private $signatureKeyset;
|
||||
|
||||
private $encryptionKeyset;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $mandatoryClaims;
|
||||
|
||||
private $continueOnDecryptionFailure;
|
||||
|
||||
public function __construct(
|
||||
JWSLoaderFactory $jwsLoaderFactory,
|
||||
?JWELoaderFactory $jweLoaderFactory,
|
||||
ClaimCheckerManagerFactory $claimCheckerManagerFactory,
|
||||
array $claimChecker,
|
||||
array $jwsHeaderChecker,
|
||||
array $mandatoryClaims,
|
||||
array $signatureAlgorithms,
|
||||
string $signatureKeyset,
|
||||
?bool $continueOnDecryptionFailure,
|
||||
?array $jweHeaderChecker,
|
||||
?array $keyEncryptionAlgorithms,
|
||||
?array $contentEncryptionAlgorithms,
|
||||
?string $encryptionKeyset
|
||||
) {
|
||||
$this->jwsLoader = $jwsLoaderFactory->create(['jws_compact'], $signatureAlgorithms, $jwsHeaderChecker);
|
||||
if ($jweLoaderFactory !== null && $keyEncryptionAlgorithms !== null && $contentEncryptionAlgorithms !== null && $jweHeaderChecker !== null) {
|
||||
$this->jweLoader = $jweLoaderFactory->create(['jwe_compact'], $keyEncryptionAlgorithms, $contentEncryptionAlgorithms, [], $jweHeaderChecker);
|
||||
$this->continueOnDecryptionFailure = $continueOnDecryptionFailure;
|
||||
}
|
||||
$this->signatureKeyset = JWKSet::createFromJson($signatureKeyset);
|
||||
$this->encryptionKeyset = $encryptionKeyset ? JWKSet::createFromJson($encryptionKeyset) : null;
|
||||
$this->claimCheckerManager = $claimCheckerManagerFactory->create($claimChecker);
|
||||
$this->mandatoryClaims = $mandatoryClaims;
|
||||
}
|
||||
|
||||
public function load(string $token): array
|
||||
{
|
||||
$token = $this->loadJWE($token);
|
||||
$data = $this->loadJWS($token);
|
||||
try {
|
||||
$this->claimCheckerManager->check($data, $this->mandatoryClaims);
|
||||
} catch (MissingMandatoryClaimException|InvalidClaimException $e) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, $e->getMessage(), $e, $data);
|
||||
} catch (\Throwable $e) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Unable to load the token', $e, $data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function loadJWS(string $token): array
|
||||
{
|
||||
$payload = null;
|
||||
$data = null;
|
||||
$signature = null;
|
||||
try {
|
||||
$jws = $this->jwsLoader->loadAndVerifyWithKeySet($token, $this->signatureKeyset, $signature);
|
||||
} catch (\Throwable $e) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be loaded or the signature cannot be verified.');
|
||||
}
|
||||
if ($signature !== 0) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token shall contain only one signature.');
|
||||
}
|
||||
|
||||
$payload = $jws->getPayload();
|
||||
if (!$payload) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.');
|
||||
}
|
||||
|
||||
$data = json_decode($payload, true);
|
||||
if (!is_array($data)) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function loadJWE(string $token): string
|
||||
{
|
||||
if (!$this->jweLoader) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$recipient = null;
|
||||
try {
|
||||
$jwe = $this->jweLoader->loadAndDecryptWithKeySet($token, $this->encryptionKeyset, $recipient);
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->continueOnDecryptionFailure === true) {
|
||||
return $token;
|
||||
}
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be decrypted.', $e);
|
||||
}
|
||||
$token = $jwe->getPayload();
|
||||
if (!$token || $recipient !== 0) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token has no valid content.');
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user