我正在开发一个具有独立前端和后端的应用程序。我需要创建受 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?或者这是在前端完成的,如果是的话怎么做?
谢谢!
您提到,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;
如果这对您有帮助或者您还有其他问题,请告诉我