PHP中的会话超时:最佳实践

36

session.gc_maxlifetimesession_cache_expire() 实际上有什么区别?假设我想让用户的会话在15分钟非活动后失效(而不是在首次打开15分钟后失效),哪一个会帮助我实现这一点?

我还知道可以使用session_set_cookie_params()设置用户cookie在一定时间后过期,但是cookie过期和服务器端实际会话过期不同,这样做会删除会话吗?

我想到的另一种解决方案是在每个请求上简单地执行$_SESSION['last_time'] = time(),并将会话与当前时间进行比较,根据此删除会话。我希望有更多内置的机制来处理这个问题。

谢谢。


当我经过一些研究后遇到了这个问题,可以在此stackoverflow问题上得到非常详细的答案,特别是为什么更新“session.gc_maxlifetime”和“session.cookie_lifetime”都不是可靠的方法。 - Peter Host
4个回答

59

我花了一些时间寻找一个关于php.ini服务器设置如何使会话过期的好答案。我找到了很多信息,但花了一段时间才弄清楚为什么这些设置能够起作用。如果你和我一样,这可能对你有所帮助:

会话被存储为cookie(客户端电脑上的文件)或者作为服务器端文件存储在服务器上。这两种方法都有优缺点。

对于存储在服务器上的会话,使用了三个变量。

session.gc_probability session.gc_divisor session.gc_maxlifetime

(session.gc_probability/session.gc_divisor) 产生垃圾回收程序运行的概率。当垃圾回收器运行时,它会检查那些至少有 session.gc_maxlifetime 时间没有被访问的会话文件并将其删除。

这在论坛帖子中已经解释得很清楚了(尤其是这个!)- 但是以下问题确实会出现:

1.) 这种概率是如何应用的?服务器什么时候掷骰子?

A: 每次在服务器上调用任何活动会话期间的 session_start() 时,服务器都会掷骰子。这意味着,如果您使用 session.gc_probability = 1 和 session.gc_divisor = 100 的默认值,则每调用 100 次 session_start(),您应该看到垃圾收集器大约运行一次。

2.) 在低流量服务器上会发生什么?

A: 当调用 session_start() 时,它首先刷新会话并使会话值对您可用。这会更新服务器上会话文件的时间。然后它掷骰子,如果赢了(100 次中的 1 次),则调用垃圾收集器。然后,垃圾收集器检查所有会话 ID 文件,并查看是否有任何符合删除条件的文件。

这意味着如果服务器上只有您一个人,您的会话将永远不会失效, 似乎更改设置没有任何影响。假设您将session.gc_maxlifetime更改为10, 将session.gc_probability更改为100。这意味着垃圾回收器运行的概率为100%, 它将清除任何最后一次访问时间超过10秒的会话文件。

如果您是服务器上唯一的用户,则您的会话将不会被删除。 您需要至少有1个其他活动会话才能使您的会话失效。

因此,在低容量服务器或低容量时间上,可能要比session.gc_maxlifetime长得多, 垃圾回收器实际上才会运行并且会话实际上才会被删除。 而不知道如何工作,这可能对您来说完全是随机的。

3.) 为什么他们使用概率?

A:性能。在高流量服务器上,您不希望垃圾回收器在每个session_start()请求中运行。 这会不必要地使服务器变慢。因此,根据服务器流量的不同,您可能需要增加 或减少垃圾回收器运行的概率。

希望这能为你解决问题。如果你和我一样,尝试了session.gc_maxlifetime却似乎没有起作用(因为你在开发服务器上尝试,以免打扰其他人),那么希望这篇文章能帮你省去一些困惑。
祝你好运!

3
非常信息丰富的回答!应该被加入到官方的PHP文档中。谢谢! - Aleksandr Makov
非常清晰的答案...但我想指出,即使您使用PHP的默认基于文件的会话,仍会向用户的浏览器发送一个cookie。这是一个会话cookie(传统上在关闭选项卡/窗口时过期),仅包含其会话标识符。因此,会话“问题”可能不仅限于服务器设置。如果用户的浏览器在选项卡/窗口关闭后“记住”此cookie,则他们可能会失去他们本来希望保留的会话。同样,您对此cookie的设置可能会影响他们保持会话的能力。 - simonhamp
1
在Debian/Ubuntu发行版中,默认情况下PHP禁用其会话垃圾回收机制。相反,它每半小时运行一个cron作业(请参见脚本/etc/cron.d/php5),以清除/var/lib/php5/目录中的会话文件。 - apurkrt

41
每次调用session_start时,会更新会话文件的时间戳(如果存在),用于计算是否已超过session.gc_maxlifetime。
更重要的是,您不能依赖于会话在session.gc_maxlifetime时间后过期。
PHP在加载当前会话后对过期会话进行垃圾回收,并使用session.gc_probabilitysession.gc_divisor计算垃圾回收运行的概率。默认情况下,它是1%的概率。
如果访问者数量较少,则有可能无活动用户可以访问应该已过期并被删除的会话。 如果这很重要,则需要在会话中存储时间戳并计算用户无活动时间。
此示例替换session_start并强制执行超时:
function my_session_start($timeout = 1440) {
    ini_set('session.gc_maxlifetime', $timeout);
    session_start();

    if (isset($_SESSION['timeout_idle']) && $_SESSION['timeout_idle'] < time()) {
        session_destroy();
        session_start();
        session_regenerate_id();
        $_SESSION = array();
    }

    $_SESSION['timeout_idle'] = time() + $timeout;
}

2
如果按照这个答案操作,请注意在不稳定的网络和可能存在竞争条件的情况下使用session_regenerate_id函数:http://php.net/manual/en/function.session-regenerate-id.php - ryanm

6

session.gc_maxlifetime 是基于会话文件最后修改时间的。所以每次会话文件被修改或在另一个页面调用 session_start() 时,gc_maxlifetime 的倒计时就会重新开始,用户保持“登录”状态。这是您要查找的值。您可以通过在 PHP 文件中使用 ini_set() 进行修改,或者如果您有访问权限,则可以编辑 php.ini。

session_cache_expire() 只控制 HTTP “Expires” 标头。此标头控制下载的页面内容在用户浏览器缓存中保留的时间。


感谢您对session_cache_expire()的解释。我无法从PHP文档中理解这个函数。 - Petr 'PePa' Pavel

1

要检查当前值,此代码将很有帮助:

$gc_maxlifetime = ini_get('session.gc_maxlifetime');
$gc_probability = ini_get('session.gc_probability');
$gc_divisor     = ini_get('session.gc_divisor');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接