Commit b71d04b5 authored by i.vasilenko@iq-adv.ru's avatar i.vasilenko@iq-adv.ru
Browse files

Merge branch 'redis_token' into 'main'

add redis token ttl

See merge request !14
parents 5c77b6a6 7b5b50eb
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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 ###
+1 −1
Original line number Diff line number Diff line
@@ -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
+15 −0
Original line number Diff line number Diff line
@@ -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
@@ -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%'
@@ -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:
+46 −0
Original line number Diff line number Diff line
@@ -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
     *
@@ -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
+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