Symfony 6.2会话过期后无法重定向问题求助
Symfony 6.2 会话超时监听器无法跳转的修复方案
核心问题排查
- 事件类型不匹配:你订阅了
KernelEvents::RESPONSE事件,但onRequest方法接收的是RequestEvent——RESPONSE事件对应的是ResponseEvent,这直接导致方法无法正确触发,setResponse自然无效。 - 事件阶段错误:会话超时检测应该在请求处理的早期(
KernelEvents::REQUEST阶段)执行,响应阶段已经接近请求处理完成,此时设置的响应很容易被后续逻辑覆盖。 - 依赖缺失:你尝试用
UrlGenerator但没在构造函数中注入,导致无法正常生成路由URL。
修复后的监听器代码
namespace App\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class SessionTimeoutListener implements EventSubscriberInterface { private $requestStack; private $csrfTokenManager; private $urlGenerator; public function __construct( CsrfTokenManagerInterface $csrfTokenManager, RequestStack $requestStack, UrlGeneratorInterface $urlGenerator ) { $this->requestStack = $requestStack; $this->csrfTokenManager = $csrfTokenManager; $this->urlGenerator = $urlGenerator; } public static function getSubscribedEvents() { return [ // 改用REQUEST事件,高优先级确保早执行 KernelEvents::REQUEST => [['onRequest', 100]], KernelEvents::RESPONSE => [['refreshSessionCookie', 0]], ]; } public function refreshSessionCookie(ResponseEvent $event) { $request = $event->getRequest(); if (!$request->hasSession()) { return; } $session = $request->getSession(); if ($session->isStarted() && $event->getResponse()) { $response = $event->getResponse(); $cookieLifetime = 20; // 复用会话原有Cookie参数,避免不匹配 $cookie = new \Symfony\Component\HttpFoundation\Cookie( $session->getName(), $session->getId(), time() + $cookieLifetime, $session->getCookieParams()['path'], $session->getCookieParams()['domain'], $session->getCookieParams()['secure'], $session->getCookieParams()['httponly'], false, $session->getCookieParams()['samesite'] ); $response->headers->setCookie($cookie); } } public function onRequest(RequestEvent $event) { $request = $event->getRequest(); // 跳过子请求,只处理主请求 if (!$event->isMainRequest()) { return; } // 检查会话状态 if (!$request->hasSession() || !$request->getSession()->isStarted()) { return; } // 跳过未认证用户 if (!$request->getSession()->has('_security_main')) { return; } // 跳过未接受Cookie的请求 if (!$request->cookies->has('accepted_cookies')) { return; } $session = $request->getSession(); $lastUsedTimestamp = $session->getMetadataBag()->getLastUsed(); $currentTime = time(); $sessionTimeout = 20; if ($currentTime - $lastUsedTimestamp > $sessionTimeout) { $session->invalidate(); $this->csrfTokenManager->refreshToken('security.csrf.token_manager'); // 用路由生成器生成正确跳转地址 $logoutUrl = $this->urlGenerator->generate('homepage_default', ['_locale' => 'cs']); $response = new RedirectResponse($logoutUrl); $event->setResponse($response); } } }
配置调整
- 服务配置更新:补上
UrlGenerator的注入:
# config/services.yaml App\EventListener\SessionTimeoutListener: tags: - { name: kernel.event_subscriber } arguments: - '@security.csrf.token_manager' - '@request_stack' - '@router'
- 会话配置优化:确保垃圾回收周期大于Cookie有效期,避免会话提前被清理:
# config/packages/framework.yaml session: handler_id: 'session.handler.native_file' cookie_secure: true cookie_samesite: lax save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%' cookie_lifetime: 20 gc_maxlifetime: 60 # 必须大于cookie_lifetime gc_probability: 1 gc_divisor: 100
关键注意点
- 会话检测必须在
REQUEST阶段执行,此时设置响应能直接终止后续逻辑,不会被覆盖。 - 刷新Cookie时复用会话原有参数,避免出现Cookie不匹配导致的会话失效异常。
- 始终过滤子请求,防止对AJAX、嵌入请求等误处理。
内容的提问来源于stack exchange,提问作者Jegi
相关产品推荐
相关产品推荐

