如何撤销JWT令牌?

问题描述 投票:0回答:1

我使用 symfony、lexik 来生成令牌,使用 gesdinet 来刷新令牌。 我在注销时遇到问题。

lexik_jwt_authentication:
    secret_key: '%env(resolve:JWT_SECRET_KEY)%'
    public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
    pass_phrase: '%env(JWT_PASSPHRASE)%'
    token_ttl: 300 # 5min
gesdinet_jwt_refresh_token:
  refresh_token_class: App\Entity\RefreshToken # Scaffolded by the bundle recipe
  ttl: 7200 # 2h in seconds
  single_use: true

  # Use cookies for the refresh token
  cookie:
    enabled: true
    remove_token_from_body: true
    # Cookie parameters
    http_only: true
    same_site: strict # tylko dla HTTPS
    secure: true
    path: /api/token
    domain: null

symfony.yaml

security:
    enable_authenticator_manager: true
    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email

    firewalls:
        login:
            pattern: ^/api/login
            stateless: true
            json_login:
                check_path: /api/login_check
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
            logout:
                path: app_logout
                csrf_token_generator: security.csrf.token_manager

        refresh_token:
            pattern: ^/api/token
            stateless: true
            refresh_jwt:
                check_path: gesdinet_jwt_refresh_token

        api:
            pattern: ^/api
            stateless: true
            jwt: ~

    access_control:
        - { path: ^/api/login, roles: PUBLIC_ACCESS }
        - { path: ^/api/(login|refresh), roles: PUBLIC_ACCESS }
        - { path: ^/api,       roles: IS_AUTHENTICATED_FULLY }

注销控制器:

use App\Entity\RefreshToken;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface as CsrfTokenStorageInterface;

class LogoutController extends AbstractController
{
    public function __construct(
        private RequestStack              $requestStack,
        private TokenStorageInterface     $tokenStorage,
        private CsrfTokenStorageInterface $csrfTokenStorage,
        private EntityManagerInterface $entityManager
    ) {
    }

    #[Route('/api/logout', name: 'app_logout')]
    public function logout(): Response
    {
        $token = $this->tokenStorage->getToken();

        if ($token !== null) {
            $this->tokenStorage->setToken(null);
        }

        $csrfTokenId = $this->requestStack->getCurrentRequest()->get('_csrf_token_id');

        if ($csrfTokenId !== null) {
            $this->csrfTokenStorage->removeToken($csrfTokenId);
        }

        $tokens = $this->entityManager->getRepository(RefreshToken::class)->findBy([
            'username' => $token->getUserIdentifier()
        ]);

        foreach ($tokens as $item) {
            $this->entityManager->remove($item);
        }
        $this->entityManager->flush();

        return new Response();
    }
}

用户实体:

#[ApiResource(
    operations: [
        new Get(),
        new Get(name: 'current_user', uriTemplate: '/user'),
    ],
    normalizationContext: ['groups' => ['api']],
    denormalizationContext: ['groups' => ['api']]
)]
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
   ...
}

RefreshToken实体:

#[ORM\Entity]
#[ORM\Table(name: 'refresh_tokens')]
class RefreshToken extends BaseRefreshToken
{
}

苏。我创建了获取当前登录用户的端点,然后通过使用实体操作的过滤器将其拉出。

final class CurrentUserExtension implements QueryItemExtensionInterface
{
    public function __construct(private readonly TokenStorageInterface $tokenStorage)
    {
    }

    public function applyToItem(
        QueryBuilder $queryBuilder,
        QueryNameGeneratorInterface $queryNameGenerator,
        string $resourceClass,
        array $identifiers,
        Operation $operation = null,
        array $context = []
    ): void {
        if (User::class === $resourceClass && 'current_user' === $operation->getName() && $this->tokenStorage->getToken()) {
            $this->support($queryBuilder);
        }
    }

    private function support(QueryBuilder $queryBuilder): void
    {
        $user = $this->tokenStorage->getToken()->getUserIdentifier();

        $rootAlias = $queryBuilder->getRootAliases()[0];
        $queryBuilder->andWhere(sprintf('%s.email = :current_user_email', $rootAlias));
        $queryBuilder->setParameter('current_user_email', $user);
    }
}

注销有效。我想是这样。但是在发送获取当前用户的请求后,我仍然得到它,但我不应该得到它,因为我已经注销了。 此外,我将令牌转储到扩展中,它仍然存在,尽管它不应该存在。 新令牌也会添加到refresh_token 表中。 该怎么办?怎样生活?

symfony jwt refresh-token lexikjwtauthbundle
1个回答
0
投票

我认为不可能撤销 JWT,因为没有数据库用于存储它,并且它会一直存在直到过期。

要撤销刷新令牌,您应该执行:

php bin/console gesdinet:jwt:revoke TOKEN
© www.soinside.com 2019 - 2024. All rights reserved.