Loading app/.env.example +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ DATABASE_URL="postgresql://user:password@db:5432/symf?serverVersion=16&charset=u JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem JWT_PASSPHRASE= JWT_TTL=3600 ###< lexik/jwt-authentication-bundle ### ###> symfony/mailer ### Loading app/config/packages/lexik_jwt_authentication.yaml +1 −1 Original line number Diff line number Diff line Loading @@ -2,4 +2,4 @@ lexik_jwt_authentication: secret_key: '%env(resolve:JWT_SECRET_KEY)%' public_key: '%env(resolve:JWT_PUBLIC_KEY)%' pass_phrase: '%env(JWT_PASSPHRASE)%' token_ttl: 3600 token_ttl: 604800 app/config/services.yaml +15 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ parameters: from_email: '%env(MAILER_ADDRESS)%' # Директория сохранения файлов images_directory: '%kernel.project_dir%/public/uploads/user_images' jwt_ttl: '%env(JWT_TTL)%' services: # default configuration for services in *this* file Loading @@ -25,6 +26,10 @@ services: - '../src/Entity/' - '../src/Kernel.php' App\Service\RedisToken: arguments: $ttl: '%jwt_ttl%' App\Service\Action\Classes\SaveImage: arguments: $targetDirectory: '%images_directory%' Loading Loading @@ -74,6 +79,16 @@ services: tags: - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_expired, method: onJWTExpired } acme_api.event.jwt_created_listener: class: App\Listeners\JwtListener tags: - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_created, method: onJWTCreated } acme_api.event.jwt_decoded_listener: class: App\Listeners\JwtListener tags: - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_decoded, method: onJWTDecoded } gesdinet.jwtrefreshtoken.send_token: class: App\Listeners\JwtRefreshListener arguments: Loading app/src/Listeners/JwtListener.php +46 −0 Original line number Diff line number Diff line Loading @@ -3,17 +3,27 @@ namespace App\Listeners; use App\Entity\User; use App\Service\RedisToken; use App\Service\Response\Classes\TokenResponse; use JsonException; use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent; use Psr\Cache\InvalidArgumentException; use Symfony\Component\HttpFoundation\Response; class JwtListener { public function __construct( private RedisToken $redisToken, ) { } /** * @param AuthenticationSuccessEvent $event * Loading Loading @@ -95,5 +105,41 @@ class JwtListener $response->setStatusCode(Response::HTTP_FORBIDDEN); $event->setResponse($response->getResponse()); $this->redisToken->clearOld(); } /** * @param JWTCreatedEvent $event * * @return void * * @throws InvalidArgumentException */ public function onJWTCreated(JWTCreatedEvent $event): void { $payload = $event->getData(); $this->redisToken->set($payload['username']); } /** * @param JWTDecodedEvent $event * * @return void * * @throws InvalidArgumentException */ public function onJWTDecoded(JWTDecodedEvent $event): void { if ($event->isValid()) { $payload = $event->getPayload(); if (!$this->redisToken->check($payload['username'])) { $event->markAsInvalid(); } $event->setPayload($payload); } } } No newline at end of file app/src/Service/RedisToken.php 0 → 100644 +123 −0 Original line number Diff line number Diff line <?php namespace App\Service; use App\Entity\User; use App\Redis\Redis; use Doctrine\Persistence\ManagerRegistry; use Psr\Cache\InvalidArgumentException; class RedisToken { private int $ttl; private Redis $redis; public function __construct( int $ttl, private ManagerRegistry $doctrine ) { $this->ttl = $ttl; $this->redis = Redis::getInstance(); } /** * Установка времени создания токена * * @param string $username * * @return int * * @throws InvalidArgumentException */ public function set(string $username): int { $time = time(); $this->redis->set($this->format($this->getId($username)), $time); return $time; } /** * Получение времени * * @param string $username * * @return bool * * @throws InvalidArgumentException */ public function check(string $username): bool { $time = $this->redis->get($this->format($this->getId($username))); if (!$time) { $time = $this->set($username); } $time += $this->ttl; $now = time(); if ($now > $time) { $this->delete($username); return false; } $this->set($username); return true; } /** * Удаление времени * * @param string $username * * @return void */ public function delete(string $username): void { if ($id = $this->getId($username)) { $this->redis->delete($this->format($id)); } } /** * Очистка прошедшего времени * * @return void * * @throws InvalidArgumentException */ public function clearOld(): void { $now = time(); $users = $this->doctrine->getManager()->getRepository(User::class)->findAll(); foreach ($users as $user) { $code = $this->format($user->getId()); if ($this->redis->has($code)) { $time = $this->redis->get($code); if ($now > $time) { $this->redis->delete($code); } } } } private function format(int $id): string { return 'token_ttl_' . $id; } private function getId(string $username): ?int { $user = $this->doctrine->getManager()->getRepository(User::class)->findOneBy(['email' => $username]); if ($user) { return $user->getId(); } return null; } } No newline at end of file Loading
app/.env.example +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ DATABASE_URL="postgresql://user:password@db:5432/symf?serverVersion=16&charset=u JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem JWT_PASSPHRASE= JWT_TTL=3600 ###< lexik/jwt-authentication-bundle ### ###> symfony/mailer ### Loading
app/config/packages/lexik_jwt_authentication.yaml +1 −1 Original line number Diff line number Diff line Loading @@ -2,4 +2,4 @@ lexik_jwt_authentication: secret_key: '%env(resolve:JWT_SECRET_KEY)%' public_key: '%env(resolve:JWT_PUBLIC_KEY)%' pass_phrase: '%env(JWT_PASSPHRASE)%' token_ttl: 3600 token_ttl: 604800
app/config/services.yaml +15 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ parameters: from_email: '%env(MAILER_ADDRESS)%' # Директория сохранения файлов images_directory: '%kernel.project_dir%/public/uploads/user_images' jwt_ttl: '%env(JWT_TTL)%' services: # default configuration for services in *this* file Loading @@ -25,6 +26,10 @@ services: - '../src/Entity/' - '../src/Kernel.php' App\Service\RedisToken: arguments: $ttl: '%jwt_ttl%' App\Service\Action\Classes\SaveImage: arguments: $targetDirectory: '%images_directory%' Loading Loading @@ -74,6 +79,16 @@ services: tags: - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_expired, method: onJWTExpired } acme_api.event.jwt_created_listener: class: App\Listeners\JwtListener tags: - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_created, method: onJWTCreated } acme_api.event.jwt_decoded_listener: class: App\Listeners\JwtListener tags: - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_decoded, method: onJWTDecoded } gesdinet.jwtrefreshtoken.send_token: class: App\Listeners\JwtRefreshListener arguments: Loading
app/src/Listeners/JwtListener.php +46 −0 Original line number Diff line number Diff line Loading @@ -3,17 +3,27 @@ namespace App\Listeners; use App\Entity\User; use App\Service\RedisToken; use App\Service\Response\Classes\TokenResponse; use JsonException; use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent; use Psr\Cache\InvalidArgumentException; use Symfony\Component\HttpFoundation\Response; class JwtListener { public function __construct( private RedisToken $redisToken, ) { } /** * @param AuthenticationSuccessEvent $event * Loading Loading @@ -95,5 +105,41 @@ class JwtListener $response->setStatusCode(Response::HTTP_FORBIDDEN); $event->setResponse($response->getResponse()); $this->redisToken->clearOld(); } /** * @param JWTCreatedEvent $event * * @return void * * @throws InvalidArgumentException */ public function onJWTCreated(JWTCreatedEvent $event): void { $payload = $event->getData(); $this->redisToken->set($payload['username']); } /** * @param JWTDecodedEvent $event * * @return void * * @throws InvalidArgumentException */ public function onJWTDecoded(JWTDecodedEvent $event): void { if ($event->isValid()) { $payload = $event->getPayload(); if (!$this->redisToken->check($payload['username'])) { $event->markAsInvalid(); } $event->setPayload($payload); } } } No newline at end of file
app/src/Service/RedisToken.php 0 → 100644 +123 −0 Original line number Diff line number Diff line <?php namespace App\Service; use App\Entity\User; use App\Redis\Redis; use Doctrine\Persistence\ManagerRegistry; use Psr\Cache\InvalidArgumentException; class RedisToken { private int $ttl; private Redis $redis; public function __construct( int $ttl, private ManagerRegistry $doctrine ) { $this->ttl = $ttl; $this->redis = Redis::getInstance(); } /** * Установка времени создания токена * * @param string $username * * @return int * * @throws InvalidArgumentException */ public function set(string $username): int { $time = time(); $this->redis->set($this->format($this->getId($username)), $time); return $time; } /** * Получение времени * * @param string $username * * @return bool * * @throws InvalidArgumentException */ public function check(string $username): bool { $time = $this->redis->get($this->format($this->getId($username))); if (!$time) { $time = $this->set($username); } $time += $this->ttl; $now = time(); if ($now > $time) { $this->delete($username); return false; } $this->set($username); return true; } /** * Удаление времени * * @param string $username * * @return void */ public function delete(string $username): void { if ($id = $this->getId($username)) { $this->redis->delete($this->format($id)); } } /** * Очистка прошедшего времени * * @return void * * @throws InvalidArgumentException */ public function clearOld(): void { $now = time(); $users = $this->doctrine->getManager()->getRepository(User::class)->findAll(); foreach ($users as $user) { $code = $this->format($user->getId()); if ($this->redis->has($code)) { $time = $this->redis->get($code); if ($now > $time) { $this->redis->delete($code); } } } } private function format(int $id): string { return 'token_ttl_' . $id; } private function getId(string $username): ?int { $user = $this->doctrine->getManager()->getRepository(User::class)->findOneBy(['email' => $username]); if ($user) { return $user->getId(); } return null; } } No newline at end of file