如何在PHP中更改会话超时时间?

185

我希望延长php会话超时时间。

我知道可以通过修改php.ini文件来实现,但是我没有权限访问它。

那么只使用php代码是否可能实现?


2
http://php.net/manual/en/function.ini-set.php - matino
1
相关的,这是在php.ini文件中,但我认为你可以像@matino所说的那样使用ini_set函数。https://dev59.com/1XRB5IYBdhLWcg3wxZ1K - J-Rou
7个回答

369

如果您想要严格保证,必须在代码中实现会话超时并设定一个时间限制;这是唯一的方法,您可以绝对确信,在X分钟的不活动后,没有任何会话会被保留。

如果放宽这个要求有一些可接受,并且您愿意将持续时间的限制改为下限,那么您可以轻松地进行设置,而无需编写自定义逻辑。

在宽松环境中更方便:如何和为什么

如果您的会话是使用cookie实现的(很可能是这样),并且如果客户端没有恶意,则可以通过调整某些参数来设置会话持续时间的上限。如果您正在使用PHP的默认会话处理方式来处理cookie,则可以通过设置session.gc_maxlifetime以及session_set_cookie_params来实现:

// server should keep session data for AT LEAST 1 hour
ini_set('session.gc_maxlifetime', 3600);

// each client should remember their session id for EXACTLY 1 hour
session_set_cookie_params(3600);

session_start(); // ready to go!

这可以通过配置服务器,在一小时的不活动时间后保留会话数据,并指示客户端在同样的时间范围内“忘记”他们的会话ID来实现。 要达到预期的结果,这两个步骤都是必需的。

  • 如果您不告诉客户端在一个小时后忘记他们的会话ID(或者如果客户端是恶意的并选择忽略您的指令),他们将继续使用相同的会话ID,其有效持续时间将是不确定的。这是因为在服务器端生命周期已过期的会话不会立即进行垃圾回收,而只有当会话GC启动时才会进行垃圾回收。(查看文档)

    GC是一个可能具有昂贵开销的进程,因此通常概率相对较小甚至为零(访问量大的网站可能完全放弃概率性GC,并安排在每X分钟后台发生)。在非合作客户的情况下,无论哪种情况,有效会话寿命的下限将是session.gc_maxlifetime,但上限将是不可预测的。

  • 如果您不将session.gc_maxlifetime设置为相同的时间范围,则服务器可能会在此之前丢弃空闲会话数据;在这种情况下,仍记得其会话ID的客户端将呈现该ID,但服务器将找不到与该会话关联的数据,实际上表现为会话刚刚开始。

在关键环境中实现确定性

您可以通过使用自定义逻辑在会话不活动期间还设置一个上限来使事情完全可控;与上面的下限一起,这将产生一个严格的设置。

通过将上限与其他会话数据一起保存来实现:

session_start(); // ready to go!

$now = time();
if (isset($_SESSION['discard_after']) && $now > $_SESSION['discard_after']) {
    // this session has worn out its welcome; kill it and start a brand new one
    session_unset();
    session_destroy();
    session_start();
}

// either new or old, it should live at most for another hour
$_SESSION['discard_after'] = $now + 3600;

会话 ID 持久性

到目前为止,我们并不关心每个会话 ID 的确切值,只关心数据存在的时间足够长。请注意,在(不太可能发生的)情况下您需要关注会话 ID 时,必须谨慎处理并使用 session_regenerate_id 在需要重新生成会话 ID 时。


问题:如果每分钟调用一次,它的限制会增加吗?例如在10:00调用它,那么它的限制将是11:00,在1分钟后的10:01,限制将变成11:01吗? - oneofakind
@oneofakind:如果你打电话,具体是打给谁? - Jon
1
这些代码:ini_set('session.gc_maxlifetime', 3600);session_set_cookie_params(3600); - oneofakind
@oneofakind:是的,但只有在您调用session_start()(否则根本没有效果)并且只有在您在session_start之前始终调用这两个函数时(否则gc_maxlifetime可能会影响当前打开的所有会话,而session_set_cookie_params只能影响使用当前请求开始的新会话)。 - Jon
@Jon 如果我再次调用session_start(),会重置$_SESSION中的所有内容吗?如果你说“可能会影响所有会话”,那么具体是怎样的呢?感谢您的回复。 - oneofakind
显示剩余6条评论

41
如果您使用PHP的默认会话处理方式,可靠地更改所有平台上的会话持续时间的唯一方法是更改php.ini。这是因为在某些平台上,垃圾收集是通过运行每个一定时间的脚本(一个脚本)直接从php.ini读取实现的,因此在运行时尝试更改它(例如通过ini_set())是不可靠的,并且很可能无法正常工作。
例如,在Debian Linux系统中,默认情况下通过将session.gc_probability=0设置为禁用PHP内部垃圾回收,并且是通过/etc/cron.d/php来完成的,该文件在XX:09和XX:39(即每半个小时)运行。此cron作业查找比配置中指定的session.gc_maxlifetime旧的会话,如果找到任何会话,则将其删除。因此,在这些系统中ini_set('session.gc_maxlifetime', ...)被忽略。这也解释了为什么在这个问题中:PHP sessions timing out too quickly,OP在一个主机上遇到问题,但切换到另一个主机后问题消失了。
因此,考虑到您无法访问php.ini,如果要实现可移植性,使用默认会话处理不是一个选项。显然,延长cookie的生存期对您的主机已经足够,但如果您想要一个可靠的解决方案,即使您更换主机也可以正常工作,您必须使用其他替代方法。
可用的替代方法包括:
  1. 在 PHP 中设置一个不同的会话(保存)处理程序,将您的会话保存到不同的目录或数据库中,如 PHP:自定义会话处理程序(PHP手册) 中所指定,以便 cron 作业无法访问它,只有 PHP 的内部垃圾回收才会发生。这个选项可能可以使用 ini_set() 来设置 session.gc_maxlifetime,但我更喜欢在我的 gc() 回调中忽略 maxlifetime 参数并自行确定最大生存期。

  2. 完全忘记 PHP 内部会话处理,并实现自己的会话管理。此方法有两个主要缺点:您将需要自己的全局会话变量,因此失去了 $_SESSION 超全局变量的优势,而且需要更多的代码,因此存在更多的漏洞和安全隐患。最重要的是,会话标识符应该由加密安全的随机或伪随机数生成,以避免会话 ID 可预测性(导致可能的会话劫持),而这在 PHP 中不太容易实现。主要优点是它将在所有平台上一致工作,并且您对代码拥有完全控制权。这是例如 phpBB 论坛软件采取的方法(至少是版本 1;我不确定更近期的版本)。

session_set_save_handler()文档中有一个示例。虽然示例很长,但我将在此处重现它,并进行相关修改以延长会话持续时间。请注意,还包括session_set_cookie_params()以增加cookie的生存期。

<?php
class FileSessionHandler
{

    private $savePath;
    private $lifetime;

    function open($savePath, $sessionName)
    {
        $this->savePath = 'my_savepath'; // Ignore savepath and use our own to keep it safe from automatic GC
        $this->lifetime = 3600; // 1 hour minimum session duration
        if (!is_dir($this->savePath)) {
            mkdir($this->savePath, 0777);
        }

        return true;
    }

    function close()
    {
        return true;
    }

    function read($id)
    {
        return (string)@file_get_contents("$this->savePath/sess_$id");
    }

    function write($id, $data)
    {
        return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
    }

    function destroy($id)
    {
        $file = "$this->savePath/sess_$id";
        if (file_exists($file)) {
            unlink($file);
        }

        return true;
    }

    function gc($maxlifetime)
    {
        foreach (glob("$this->savePath/sess_*") as $file) {
            if (filemtime($file) + $this->lifetime < time() && file_exists($file)) { // Use our own lifetime
                unlink($file);
            }
        }

        return true;
    }
}

$handler = new FileSessionHandler();
session_set_save_handler(
    array($handler, 'open'),
    array($handler, 'close'),
    array($handler, 'read'),
    array($handler, 'write'),
    array($handler, 'destroy'),
    array($handler, 'gc')
    );

// the following prevents unexpected effects when using objects as save handlers
register_shutdown_function('session_write_close');

session_set_cookie_params(3600); // Set session cookie duration to 1 hour
session_start();
// proceed to set and retrieve values by key from $_SESSION

第二种方法比较复杂,基本上你需要自己重新实现所有的会话函数。我这里不会详细介绍。


有人可以确认一下吗? - Oli
@Oli:经过初步阅读,它看起来是正确的。您可能还想查看https://dev59.com/1XRB5IYBdhLWcg3wxZ1K,但如果您无法访问`php.ini`,则您的实际选项会严重受限。 - Jon
此外,在Ubuntu 14上,似乎/usr/lib/php5/maxlifetime不会计算低于24分钟的值。因此,您无法将会话超时设置为低于该值。 - Henry
彻底忘记PHP内部会话处理并实现自己的会话管理。天哪,这是危险的建议。不可避免地会导致安全噩梦。 - Kzqai
1
@Kzqai 我也注意到“它需要更多的代码,因此存在更多的漏洞和安全隐患”。这不是建议,我只是列举了一些替代方案,但如果你有改进的建议,请提出。 - Pedro Gimeno
session.gc_probability 根据文档默认为 1 - https://www.php.net/manual/zh/session.configuration.php#ini.session.gc-probability - Jacob Thomason

12

针对使用共享主机或添加域名的注意事项 =

要使您的设置生效,必须为添加的域名使用不同的保存会话目录,方法是使用 php_value session.save_path folderA/sessionsA

因此,请在根服务器下创建一个文件夹,而不是 public_html 中,并且不能从外部公开访问。 对于我的 cpanel/server,文件夹权限为 0700 运行良好。 可以尝试一下...

# Session timeout, 2628000 sec = 1 month, 604800 = 1 week, 57600 = 16 hours, 86400 = 1 day
ini_set('session.save_path', '/home/server/.folderA_sessionsA');
ini_set('session.gc_maxlifetime', 57600); 
ini_set('session.cookie_lifetime', 57600);
# session.cache_expire is in minutes unlike the other settings above         
ini_set('session.cache_expire', 960);
ini_set('session.name', 'MyDomainA');

session_start();之前

或者将此放入您的.htaccess文件中。

php_value session.save_path /home/server/.folderA_sessionsA
php_value session.gc_maxlifetime 57600
php_value session.cookie_lifetime 57600
php_value session.cache_expire 57600
php_value session.name MyDomainA

经过多次研究和测试,这对于共享cpanel/php7服务器运行良好。非常感谢:NoiS


我只需要像你说的那样更改save_path,在我的共享托管服务器上它就可以工作了。 - Aun Rizvi

4

针对使用 Plesk 遇到以上任何问题的用户,我想提供一些帮助。这个问题曾经让我很烦恼,因为在 PHP 脚本中设置 session.gc_maxlifetime 是无效的,Plesk 有自己的垃圾回收脚本从 cron 中运行。

我在下面的链接中找到了解决方案,将 cron 作业从 hourly 改为 daily 可以避免这个问题,然后上面的最佳答案就可以使用:

mv /etc/cron.hourly/plesk-php-cleanuper /etc/cron.daily/

https://websavers.ca/plesk-php-sessions-timing-earlier-expected


3
$_SESSION['login_time'] = time();放入之前的身份验证页面中。在每个其他需要检查会话超时的页面中都使用以下代码段。
if(time() - $_SESSION['login_time'] >= 1800){
    session_destroy(); // destroy session.
    header("Location: logout.php");
    die(); // See https://thedailywtf.com/articles/WellIntentioned-Destruction
    //redirect if the page is inactive for 30 minutes
}
else {        
   $_SESSION['login_time'] = time();
   // update 'login_time' to the last time a page containing this code was accessed.
}

编辑: 仅在您已经使用了其他帖子中的小技巧或禁用垃圾收集并想手动检查会话持续时间时,此方法才有效。请不要忘记在重定向后添加 die(),因为某些脚本/机器人可能会忽略它。此外,直接使用 session_destroy() 销毁会话可能是更好的选择,以防恶意客户端或机器人。


0

不行。 如果您无法访问php.ini,就不能保证更改会产生任何效果。

我怀疑您并不需要延长会话时间。
目前的超时时间非常合理,没有理由要延长它。


嗨Col,我一直在这个地方寻找与你联系的方法。我看到你在我的上一篇帖子(星期天)被关闭时给了我一些建议。我忙于另一个项目,现在它已经消失了。我真的很想尝试你的建议。有没有办法找到你写的内容? - The old dog
据我所见,它不仅被关闭了,而且还被删除了。这些人没有荣誉感。是的,你的问题有一个我之前提到的常见解决方案。我会通过电子邮件给你写信。简而言之,就是运行两个额外的查询来获取这些 prev/next 值。SELECT id FROM gallery WHERE SortOrder > $currentsortorder LIMIT 1 - Your Common Sense
@jor,你肯定把会话和 cookie(或数据库)搞混了。 - Your Common Sense
@YourCommonSense的会话基于cookies(除了不安全的url-param方式)。 - jor
@jor 请学习一下什么是会话(session)以及它与基于cookie的机制的区别。这并不难。 - Your Common Sense
显示剩余2条评论

-1

您可以使用ini_set()从您的PHP代码中覆盖php.ini中的值。


5
session.gc_maxlifetime不是控制会话生命周期的设置。如果将session.gc_divisor设置为1,它可以被强制用于控制会话生命周期,但这样做非常糟糕。 - Jon
1
@Jon,我在SO上看到很多答案建议相反的做法,为什么会这样呢?https://dev59.com/VEbRa4cB1Zd3GeqP0XTP https://dev59.com/WGkw5IYBdhLWcg3waZ_4 - giannis christofakis
2
@yannishristofakis:gc_maxlifetime设置会话数据在多长时间后可以进行垃圾回收,如果GC在经过这么长时间后发生,会话数据将被销毁(使用默认设置时,这与会话过期相同)。但是,每次会话启动时都会以概率触发GC,因此不能保证会话实际上会过期 - 您可以绘制概率曲线与时间的关系,但它看起来不像砖墙。这只是冰山一角;请参见https://dev59.com/1XRB5IYBdhLWcg3wxZ1K - Jon
只有在托管上有一个单一的会话管理(即一个单一的网站)时,此方法才有效。否则,最短的垃圾回收超时规则将适用于其他网站。 - jor

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