在网络上进行大量搜索后,一无所获,我想知道是否有一种简单的方法可以在非活动期后自动注销通过 Symfony Security 登录的用户。例如,我希望用户在 30 分钟不活动后注销。
我使用这样的自定义用户提供程序。
但用户登录系统后,会话永不过期。即使他关闭浏览器并在几天后再次打开它,会话仍然有效。
无论如何可以通过自动方式甚至手动方式注销该用户?我怎样才能做到这一点?
您必须使用内核侦听器来实现它,这是我解决它的方法:
监听器 src/Comakai/MyBundle/Handler/SessionIdleHandler.php
namespace Comakai\MyBundle\Handler;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class SessionIdleHandler
{
protected $session;
protected $securityToken;
protected $router;
protected $maxIdleTime;
public function __construct(SessionInterface $session, TokenStorageInterface $securityToken, RouterInterface $router, $maxIdleTime = 0)
{
$this->session = $session;
$this->securityToken = $securityToken;
$this->router = $router;
$this->maxIdleTime = $maxIdleTime;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) {
return;
}
if ($this->maxIdleTime > 0) {
$this->session->start();
$lapse = time() - $this->session->getMetadataBag()->getLastUsed();
if ($lapse > $this->maxIdleTime) {
$this->securityToken->setToken(null);
$this->session->getFlashBag()->set('info', 'You have been logged out due to inactivity.');
// Change the route if you are not using FOSUserBundle.
$event->setResponse(new RedirectResponse($this->router->generate('fos_user_security_login')));
}
}
}
}
配置 src/Comakai/MyBundle/Resources/config/services.yml (Comakai/MyBundle/DependencyInjection/MyBundleExtension.php)
services:
my.handler.session_idle:
class: Comakai\MyBundle\Handler\SessionIdleHandler
arguments: ["@session", "@security.context", "@router", %session_max_idle_time%]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
现在您可以将 inparameters.yml 设置为session_max_idle_time
30 * 60 = 1800秒(或者只需将值硬编码到您想要的任何位置):
参数 app/config/parameters.yml
parameters:
...
session_max_idle_time: 1800
以下设置将注销超过 30 分钟不活动的用户。如果每 29 分钟发出一次请求,他们将永远不会被注销。请注意,这在本地环境中并不容易测试,因为垃圾收集器仅从您的请求中调用,因此永远不会达到 gc_maxlifetime!
#app/config/config.yml
session:
cookie_lifetime: 86400
gc_maxlifetime: 1800
如果您打开更多浏览器/会话并使用以下配置,则可以对此进行测试:
#app/config/config.yml
session:
cookie_lifetime: 86400
gc_maxlifetime: 1800
gc_probability: 1
gc_divisor: 1
希望有帮助!
请注意,添加:
session:
gc_probability: 1
gc_divisor: 1
仅用于在没有其他请求导致垃圾收集器删除您的会话的本地环境中测试垃圾收集器。让垃圾收集器在每个请求上运行并不意味着(或必需)在生产环境中!
如果有人想在 Symfony 4 中实现这一点,我已经更新了@coma 给出的答案,因为 security.context 已被贬值,parameters.yml 现在只是 app/config/service.yaml 的一部分,你可以注入其他变量为承包商。不过,它基本上是相同的答案,只是针对 Symfony 4 进行了调整:
监听器src/Security/SessionIdleHandler.php (或任何地方,它映射在下面的事件监听器中)
<?php
namespace App\Security;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class SessionIdleHandler
{
protected $session;
protected $securityToken;
protected $router;
protected $maxIdleTime;
public function __construct($maxIdleTime, SessionInterface $session, TokenStorageInterface $securityToken, RouterInterface $router)
{
$this->session = $session;
$this->securityToken = $securityToken;
$this->router = $router;
$this->maxIdleTime = $maxIdleTime;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) {
return;
}
if ($this->maxIdleTime > 0) {
$this->session->start();
$lapse = time() - $this->session->getMetadataBag()->getLastUsed();
if ($lapse > $this->maxIdleTime) {
$this->securityToken->setToken(null);
$this->session->getFlashBag()->set('info', 'You have been logged out due to inactivity.');
// logout is defined in security.yaml. See 'Logging Out' section here:
// https://symfony.com/doc/4.1/security.html
$event->setResponse(new RedirectResponse($this->router->generate(logout)));
}
}
}
}
参数app/config/service.yaml
parameters:
...
session_max_idle_time: 600 // set to whatever value you want in seconds
内核事件监听器app/config/service.yaml
services:
...
App.Handler.SessionIdle:
class: App\Security\SessionIdleHandler
arguments: ['%session_max_idle_time%']
tags: [{ name: kernel.event_listener, event: kernel.request }]
与 FOSUserbundle 完美搭配,谢谢。
我将此添加到内部条件中以防止匿名用户注销。
...
$isFullyAuthenticated = $this->securityContext->isGranted('IS_AUTHENTICATED_FULLY');
if ($lapse > $this->maxIdleTime && $isFullyAuthenticated == true) {
... do logout / redirect etc.
}
在 Symfony 2.4 中,以下对我来说工作得很好,有 1 小时的超时时间:
framework:
#esi: ~
translator: { fallback: %locale% }
secret: %secret%
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
http_port: 80
https_port: 443
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
templating:
engines: ['twig']
#assets_version: SomeVersionScheme
default_locale: "%locale%"
trusted_proxies: ~
session:
cookie_lifetime: 3600
fragments: ~
trusted_hosts: ~
这是我使用 Symfony 4 的示例。
使用 Session 而不是 SessionInterface,因为此接口不包含对该getFlashBag()
方法的访问。
重定向是在 onapp_login
而不是 on 上进行app_logout
,否则当前会话的 flashBag 会丢失。
$this->tokenStorage->setToken();
可以
$this->tokenStorage->reset();
通过具体类替换,但接口不允许。
你可以使用这个:
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
class SessionIdleListener
{
/**
* @var int
*/
private $maxIdleTime;
/**
* @var Session
*/
private $session;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @var RouterInterface
*/
private $router;
/**
* @var AuthorizationCheckerInterface
*/
private $checker;
public function __construct(
string $maxIdleTime,
Session $session,
TokenStorageInterface $tokenStorage,
RouterInterface $router,
AuthorizationCheckerInterface $checker
) {
$this->maxIdleTime = (int) $maxIdleTime;
$this->session = $session;
$this->tokenStorage = $tokenStorage;
$this->router = $router;
$this->checker = $checker;
}
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMasterRequest()
|| $this->maxIdleTime <= 0
|| $this->isAuthenticatedAnonymously()) {
return;
}
$session = $this->session;
$session->start();
if ((time() - $session->getMetadataBag()->getLastUsed()) <= $this->maxIdleTime) {
return;
}
$this->tokenStorage->setToken();
$session->getFlashBag()->set('info', 'You have been logged out due to inactivity.');
$event->setResponse(new RedirectResponse($this->router->generate('app_login')));
}
private function isAuthenticatedAnonymously(): bool
{
return !$this->tokenStorage->getToken()
|| !$this->checker->isGranted(AuthenticatedVoter::IS_AUTHENTICATED_FULLY);
}
}
App\EventListener\SessionIdleListener:
bind:
$maxIdleTime: '%env(APP_SESSION_MAX_IDLE_TIME)%'
$session: '@session'
tags:
- { name: kernel.event_listener, event: kernel.request }
关于什么:
#app/config/config.yml
framework:
session:
cookie_lifetime: 1800
#App/Twig/LogoutAfterMomentExtension.php
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class LogoutAfterMomentExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('logoutAfter', [$this, 'logoutAfter']),
];
}
public function logoutAfter(int $seconds)
{
return header( "refresh:".$seconds.";url=/admin/logout" );
}
}
#templates/layout.html.twig
<body>
{{ logoutAfter(5) }} #it will logout after 5 seconds
...
</body>
cookie 的生命周期是不合适的,因为它可以被客户端操纵,所以我们必须在服务器端进行到期。最简单的方法是通过合理频繁地运行的垃圾收集来实现这一点。cookie_lifetime 将设置为相对较高的值,垃圾收集 gc_maxlifetime 将设置为在所需的空闲时间段销毁会话。
framework:
#esi: ~
#translator: { fallback: "%locale%" }
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
templating:
engines: ['twig']
#assets_version: SomeVersionScheme
default_locale: "%locale%"
trusted_hosts: ~
trusted_proxies: ~
session:
# handler_id set to null will use default session handler from php.ini
#handler_id: ~
cookie_lifetime: 9999
gc_maxlifetime: 900
gc_probability: 1
gc_divisor: 2
fragments: ~
http_method_override: true