Symfony 中的 PHP JWT 令牌流程

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

我正在开发一个具有独立前端和后端的应用程序。我需要创建受 JWT 令牌保护的 API 路由。

登录/注册时,用户会取回 JWT 令牌(短期)和刷新令牌(长期)。刷新令牌存储在 HTTPOnly cookie 中。

前端将 JWT 令牌以“Bearer jwt_token”格式附加到授权标头。

我不知道JWT过期后如何处理流程。

如果刷新令牌存在于 cookie 和数据库中,并且在我生成新的 JWT 令牌之前尚未使用过。

如何在授权标头中将旧的 JWT 令牌替换为新的 JWT 令牌?

<?php

namespace AppBundle\EventListener;

use AppBundle\Entity\User;
use AppBundle\Service\AuthApiService;
use AppBundle\Service\JWTAuthService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

class ApiKeyListener
{
    private $em;
    /**
     * @var AuthApiService
     */
    private $authApiService;
    /**
     * @var JWTAuthService
     */
    private $JWTAuthService;

    private $apiKey;

    public function __construct(EntityManagerInterface $em, AuthApiService $authApiService, JWTAuthService $JWTAuthService, $apiKey)
    {
        $this->em = $em;
        $this->authApiService = $authApiService;
        $this->JWTAuthService = $JWTAuthService;
        $this->apiKey = $apiKey;
    }

    public static function getSubscribedEvents()
    {
        return [
            'kernel.request' => ['onKernelRequest', 255], // 255 ensures this listener runs before others
        ];
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        if ($request->headers->has('Authorization'))
        {
            $authorizationHeader = $request->headers->get('Authorization');
            if (strpos($authorizationHeader, 'Bearer ') === 0)
            {
                // A bearer token has been passed
                $this->validateJWT($request);
            }
        }

    }

    public function validateJWT(Request $request): void
    {
        $credentials = $request->headers->get('Authorization');
        $credentials = str_replace('Bearer ', '', $credentials);

        $isSignatureValid = $this->JWTAuthService->validateSignature($credentials);

        if (!$isSignatureValid['success']) throw new AuthenticationException($isSignatureValid['message']);

        $isActive = $this->JWTAuthService->isJWTTokenActive($credentials, $request->cookies->get('refresh_token'));

        if (!$isActive['success']) throw new AuthenticationException($isActive['error']['message']);

        setcookie('refresh_token', $isActive['data']['refresh_token'], time() + 3600 * 24 * 7, '/', null, false, true);

        $request->headers->set('Authorization', 'Bearer ' . $isActive['data']['token']);
        
        $user = $this->em->getRepository(User::class)->find($isActive['data']['user_id']);

        $request->attributes->set('user', $user->getId());
    }
}
$request->headers->set('Authorization', 'Bearer ' . $isActive['data']['token']);

这不会更改标题。我猜出于安全原因应该是这样。

前端采用 React。 我应该如何实现此功能,以便它更改新请求的 JWT?或者这是在前端完成的,如果是的话怎么做?

谢谢!

php jwt
1个回答
0
投票

您提到,JWT 刷新令牌存储为 HTTPOnly cookie。这些 cookie 通常无法通过 React 或 JavaScript 访问,但仍会随每个请求一起发送。

重要的问题是,访问令牌如何存储。由于它们作为标头传递,因此根据我的理解,它们不太可能存储为 HTTPOnly cookie。以下是前端如何处理向 API 请求添加授权标头的示例:

import axios from 'axios';

axios.defaults.headers.common['Authorization'] = () => {
  const token = document.cookie
    .split(';')
    .find(row => row.startsWith('authToken='))
    ?.split('=')[1];
  return `Bearer ${token}`;
};

function fetchData() {
  axios.get('/api/protected-data')
    .then(response => console.log(response.data))
    .catch(error => console.error(error));
}

在此示例中,我们读取名为

authToken
的 cookie,并将其作为
Authorization: Bearer {ourCookie}
附加到每个请求中。为此,不允许使用 HTTPOnly。

如果它们存储为普通 cookie,您可以选择以 JSON 形式返回新生成的令牌,或在响应中添加 Set-Cookie 标头。但是,无法使用

$request->headers->set
来实现此目的。

如果您想以 JSON 形式返回令牌,您可以使用

JsonResponse
对象:

// Assume we have a token object (e.g. from Doctrine)
return new JsonResponse([
     'token' => $newToken->getToken(),
     'refresh_token' => $newToken->getRefreshToken(),
     'expires_at' => $new_token->getExpiresAt()
]);

如果您想使用 Symfony 直接设置 cookie:

$access_cookie = Cookie::create(
     "access_token", 
     $cookieValue,
     $expireTime,
     '/', // Path (optional)
     null, // Domain (optional)
     false, // HttpOnly flag (Important to be false, since it needs to be read by React)
     true, // Secure flag (recommended for HTTPS)
     false // SameSite attribute (optional, refer to documentation)
);

$refresh_cookie = Cookie::create(
     "refresh_token", 
     $cookieValue,
     $expireTime,
     '/', // Path (optional)
     null, // Domain (optional)
     true, // HttpOnly flag (Important to be true, since it's supposed to be secured as good as possible)
     true, // Secure flag (recommended for HTTPS)
     false // SameSite attribute (optional, refer to documentation)
);

$response = new Response();
$response->headers->setCookie($access_cookie);
$response->headers->setCookie($refresh_cookie);

return $response;

Cookie 对象 Symfony

如果这对您有帮助或者您还有其他问题,请告诉我

© www.soinside.com 2019 - 2024. All rights reserved.