【问题标题】:PHP sessions expiring earlyPHP 会话提前到期
【发布时间】:2011-09-21 08:52:16
【问题描述】:

我在工作中运行的 PHP 站点出现问题,用户在几分钟后就会退出(具体时间会有所不同,但它的频率足以成为一个问题),无论他们是否积极参与是否使用该网站。

困难在于我无法重现这个问题,如果我以相同的用户使用相同的浏览器登录,我不会被注销,这表明这不是网站被完全破坏的情况。不幸的是,我无权访问用户机器来运行任何流量嗅探软件。

我已经检查的事情是:

  • 要求用户尝试不同的浏览器。这似乎无法解决问题,也不是长期解决方案,因为我无法指定客户将使用哪些浏览器。
  • 服务器时间正确,与用户机器一致。
  • 运行 Apache 的用户具有写入会话文件夹的权限,我可以看到正在创建的会话文件及其修改时间正在更新。
  • 没有使用输出缓冲函数。
  • 问题出现在各种似乎没有任何共同点的页面上(也就是说,并不是它们都使用 AJAX,或者更新数据库或其他原因)。
  • 用户只能从一台机器访问他们的帐户,即他们没有在笔记本电脑上做任何工作,切换到桌面然后想知道为什么他们在笔记本电脑上被注销(我们不允许多个同一用户同时登录)。

PHP 中的会话设置是 Debian 的默认设置,在 .htaccess 文件或其他任何地方都没有更改。主要有:

session.cookie_lifetime    0
session.gc_divisor    100
session.gc_maxlifetime    1440
session.gc_probability    0
session.save_handler    files
session.save_path    /var/lib/php5
session.use_cookies    On

Debian 通过 cron 作业删除会话,而不是使用 PHP 的垃圾收集器,这就是 gc_probability 设置为 0 的原因。我们正在运行的 PHP 版本是:PHP 5.2.6-1+lenny13 with Suhosin-Patch 0.9.6.2 (cli)(Lenny 的最新版本,我们很快就会升级到 Squeeze,但我认为这不是问题的原因)。

我们使用 Zend_Session 来管理会话,并且 Zend_Session_Namespace 的实例在每个页面上创建一次,因此会自动调用 session_start()。通过在注销页面上调用 Zend_Session::destroy() 来清除会话,因此用户应该注销的唯一方法是:

  • 如果他们明确点击注销链接(我们会在发生这种情况时进行记录,并且浏览似乎不是预先获取页面并因此将用户注销)。
  • 如果他们让会话处于非活动状态超过 24 分钟,此时 Debian 可能会删除他们的会话(有一个每半小时运行一次的 cron 作业删除所有超过 24 分钟未修改的会话)。
  • 如果他们关闭浏览器,他们的会话 cookie 的过期时间为 0 将被删除。

查看用户是否登录的检查是:

  • 他们有一个有效的会话(通过查看我们是否可以访问 $zsession->user_id 来检查)。
  • 会话表中有一行具有匹配的用户 ID 和会话 ID,并且最近一次更新是不到一小时前。我们在注销时删除这一行,这样即使会话仍然存在于磁盘上,任何人都无法在不登录的情况下访问该帐户。

谁能建议我可以尝试的其他方法?

编辑:我根据 cmets 尝试了一些额外的东西:

  • 设置 session.cookie_domain:这在 PHP 中似乎有非常奇怪的行为。如果我不设置此变量并将其保留为默认值 ''(空字符串),则对 www.domain.com 的请求将生成 www.domain.com 的 cookie。但是,如果我将 cookie_domain 设置为“www.domain.com”,则 cookie 的域为“.www.domain.com”(注意前导点,这意味着对 www.domain.com 下的所有内容都有效,例如 subsite.www .domain.com)。
  • 设置 session.cookie_lifetime:PHP 似乎不会更新每个请求的过期时间,所以如果我将 cookie_lifetime 设置为 3600,cookie 将在用户首次访问该站点一小时后过期,即使他们登录并不断使用它.

编辑 2:根据人们提出的其他问题:

  • 该站点托管在数据中心的单独 VLAN 上。访问该网站的人与该网站不在同一网络上。
  • 没有使用 IP 身份验证,也没有在会话过程的任何部分使用客户端的 IP 地址(例如,如果用户的下一个请求来自不同的 IP)。

【问题讨论】:

  • 上次我遇到了类似的问题,我错过了在某个 php 文件中正确处理会话(所有其他文件都可以)。结果是,在用户尝试离开该特定页面后会话变得无效,因此他在不同的分钟后注销,具体取决于他导航到该页面的时间。不要将此视为解决方案。将其视为一个提示,您可以在其中查找一些错误。祝你好运! ^^
  • 时区(或错误的服务器时间)在某些情况下可能会导致问题。
  • @Smar 可以,但我确实明确表示“服务器时间正确且与用户机器一致”。

标签: php session


【解决方案1】:

Debian 通过 cron 作业删除会话,而不是使用 PHP 的垃圾收集器

这很奇怪 - cron 作业中运行的代码是什么?

我们在注销时删除该行

我建议您在会话过期/被删除后将其保留 2 天(但在您当前删除它的位置将其标记为已死)。此外,开始在您的网络服务器日志中记录会话 ID。

【讨论】:

  • 这并不奇怪,Debian 多年来一直使用这种方法。实际代码运行是:09,39 * * * * root [ -x /usr/lib/php5/maxlifetime ] && [ -d /var/lib/php5 ] && find /var/lib/php5/ -type f - cmin +$(/usr/lib/php5/maxlifetime) -delete
  • 你已经检查了 /usr/lib/php5/maxlifetime 输出了什么?
  • 是的,它输出 24(这是我所期望的)。 cron 作业肯定工作正常,只删除 24 分钟未访问的会话文件。
  • @pwarning - 这对我来说似乎特别奇怪,即使多年来一直如此。 PHP 处理垃圾收集它自己的会话数据。不管 php 的设置如何,让主机系统强制执行 24 分钟的计划似乎有点愚蠢。如果我最终花了一天的时间来追踪它,我会很生气。
  • 好吧,这并不是真的“不管 PHP 的设置如何”,因为 Debian 附带了一个 php.ini 文件,该文件配置 PHP 以便不使用垃圾收集(或者,如果你想学究,设置GC 运行的概率为 0)。在许多方面,cron 作业都比 GC 好,因为您可以保证 cron 作业会运行,而 GC 是不可预测的。
【解决方案2】:

最后,答案是废弃会话并编写我自己的非常简单的 cookie 代码,它与会话的不同之处在于以下方面:

  1. 将哈希(有点像会话 ID)存储在数据库中而不是文件中。
  2. 将 cookie 设置为从现在起 3600 秒后过期(在每个页面上更新)而不是 0 秒(后者似乎会给 IE 用户带来问题,尽管我无法复制它)。
  3. 仅在用户登录或登录时发送 cookie 标头。

这不是一个理想的情况,因为有一些重新发明轮子正在进行,但我的小解决方案似乎在 PHP 会话没有工作的地方工作,并且拥有一个工作站点是最重要的。

【讨论】:

  • 10 年后读你的故事真是太高兴了! :) 精心制作的 Q+A,显示了你的坚持,以及对主题的有效把握,我几乎能感觉到挣扎。我希望我能读到更多关于它的信息。事实上,如果你觉得并记住,即使现在你也可以添加一些更新,我全都听好了。 :)
【解决方案3】:

我觉得你算改变值

session.gc_maxlifetime 

我也遇到了同样的问题。我花了很多时间在上面,然后我问我的网络服务提供商,当他得到许可后,他改变了那个山谷。现在它工作正常。

【讨论】:

  • 已经设置为 1440 秒,即 24 分钟,但用户在此之前就已被注销。
【解决方案4】:

是否有其他 php 应用程序在同一系统上运行(例如,在不同的虚拟主机下?)?他们是否也在 /var/lib/php5 中保存会话?

如果是这样,并且其中一个应用的会话垃圾收集阈值较低,它们将丢弃您应用的会话文件。

我做了很多 ZF 开发,如果我使用基于文件系统的会话,我会将它们放在 application/data/session 中而不是系统默认值中。

【讨论】:

  • 系统上只有一个应用程序,除了几个测试站点,但它们都具有相同的 GC 阈值。
【解决方案5】:

您的 session.gc_maxlifetime 设置为 1440 毫秒,即只有 1.44 秒。不应该是 1440000 毫秒 = 24 分钟吗?

【讨论】:

  • session.gc_maxlifetime 以秒为单位设置。所以 1440 24 分钟是正确的。
  • 您确定没有其他会话生命周期较短的脚本吗?因为生命周期最短的脚本会被执行。
  • gc_maxlifetime 是全局设置的,不会在任何 PHP 脚本中被覆盖。所有会话处理功能都在一个文件中执行,该文件包含在每一页上。
【解决方案6】:

您可以尝试将session.use_only_cookies 设置为1,将session.cookie_lifetime 设置为1440 秒。

【讨论】:

  • for session.cookie_lifetime 0 表示浏览器在关闭窗口时删除cookie,对于会话cookie是理想的。
  • 当然是理想的,但是例如,如果您在为浏览器设置特定权限的 Windows 域控制器下工作,则不能保证该规则有效。我在域控制器下遇到问题,即同一会话中的弹出窗口破坏了会话 cookie。在我明确设置生命周期后,我可以解决这个问题。
  • 我还没有听说过域控制器问题,我想这可能是解决方案。我会试一试,看看会发生什么 - 虽然我不确定 PHP 是否会在每个页面加载时更新会话 cookie,所以即使用户仍然处于活动状态,cookie 也有可能在 24 分钟后过期。
  • 我刚刚尝试过,我的怀疑是正确的,PHP 不会在每次页面加载时更新 cookie 的到期日期(例如,如果我现在设置 10 分钟到期,它将在 10 后到期分钟时间,无论我在此期间是否加载任何启用会话的页面)。
  • 在每次页面加载时扩展会话。 setcookie('PHPSESSID',$_COOKIE['PHPSESSID'],time()+1440,'/');
【解决方案7】:

您的网站是否在不同的域上?例如 domain.com、www.domain.com、subdomain.domain.com ?如果某些页面被重定向到不同的域(www 被视为不同的子域),那么当地址更改时会话将无法工作

编辑: 您必须重现该问题。询问您的客户他们使用什么类型的浏览器,他们在注销之前会执行什么操作,他们是否与您查看相同的站点 IP? (即您都在外部网络中或都在与站点相同的网络中)

当您设法找到问题时,请在会话有效时检查请求/响应标头,以及在会话无效时检查,然后进行比较。

【讨论】:

  • PHP 实际上会为所有子域设置会话 cookie:Set-Cookie PHPSESSID=0a4ogbefeptfndukol3pjl7tg4; path=/; domain=.thesite.com。注意域名前面的点。
  • 其实PHP并没有为所有子域设置会话cookie: Set-Cookie: PHPSESSID=428d4be9caca21acc558c0510f22717b;路径=/;安全 如果我在 Firefox 中查看 cookie,它是为 www.domain.com 设置的(我们将对 domain.com 的任何请求重定向到 www.domain.com)。
  • 您使用的是哪个版本的 PHP,因为在我的上面的输出是可见的?
  • PHP 5.2.6-1+lenny13 和 Suhosin-Patch 0.9.6.2。
  • 所以它实际上与子域相关吗?尝试为所有子域设置cookie,或确保您登录的域对于任何页面都是相同的。因此,请在任何可能的登录方式之前重定向到 www
【解决方案8】:

最后,我选择在每个页面请求上发送一个 Set-Cookie 标头,类似于 FlyBy 在其中一个 cmets 中建议的内容。现在相关的代码/逻辑是(假设 session_start() 已经被调用):

$this->session_name = session_name();
$update_cookie = isset($_COOKIE[$this->session_name]); // Check if cookie already set, as PHP will send the first Set-Cookie when the session is started
$this->logged_in = $this->checkSession(); // Function which checks whether a valid (i.e. not timed-out) session row exists in the DB
if ($this->logged_in) {
  $this->updateSession(); // Update the session row to the current time

  if ($update_cookie) {
    // Update the cookie expiry only if it existed before the login check
    setcookie($this->session_name, $_COOKIE[$this->session_name], $this->time + 3600, '/');
  }
}

我不确定为什么会这样,但我没有任何进一步的投诉,并且登录次数急剧下降(在几分钟内同一用户的日志中不再有多次登录)。

但是,我可能会在某个时候重写代码,只在客户端使用数据库行和 cookie,因为 PHP 中的会话功能有很多变量,很难找出导致问题的原因,并且会话 cookie 处理与普通 cookie 的处理方式略有不同。特别要注意setcookie函数,因为PHP启动的会话cookie的默认路径是'/',而setcookie的默认路径是当前目录路径,这些不一定相同。

【讨论】:

  • 废话,显然问题仍在发生,我已经完全没有想法了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-02
  • 2019-11-07
  • 2013-03-03
  • 1970-01-01
  • 1970-01-01
  • 2011-04-07
相关资源
最近更新 更多