引言:“为什么我的会话不会过期?”
\n\n想象一下:您正在编写一个在线商店、管理后台或个人账户。用户登录、操作、然后去喝咖啡——但一小时后,他的会话仍然有效。购物车数据还在,访问权限仍然开放。这不仅不方便,而且很危险。特别是如果网站处理支付数据。
\n\n“How do I expire a PHP session after 30 minutes?” 这个问题是 Stack Overflow 上最热门的问题之一(有数万次浏览)。这并不奇怪:初级开发人员通常认为只需设置 session.gc_maxlifetime 就能搞定。然后他们惊讶地发现会话持续了几个小时。
在本文中,我们将介绍 4 种实际方法,让 PHP 会话在 30 分钟后精确过期。您将了解为什么标准设置可能失效,以及如何根据您的需求选择合适的方法。开始吧!
\n\n一点理论:PHP 如何管理会话时间
\n\n在编写代码之前,了解影响会话“过期”的两个机制很重要:
\n\n- \n
- 会话 cookie 的生命周期——浏览器存储会话标识符(PHPSESSID)的时间。由
session.cookie_lifetime参数控制。 \n - 服务器上数据的生命周期——会话文件在 /tmp 文件夹中保留的时间。由
session.gc_maxlifetime控制。 \n
问题在于,垃圾回收器(GC)是概率性运行的。默认情况下,它在 1% 的请求中触发。这意味着即使 gc_maxlifetime = 1800(30 分钟),会话也可能再挂起一个小时,直到 GC 处理到它。
因此,仅依赖服务器设置是个坏主意。您需要“手动”检查时间。
\n\n方法 1. 经典方法:在会话中检查时间(推荐)
\n\n这是最可靠、最透明的方法。您自己在会话中存储最后活动的时间戳,并在每次请求时检查是否已过 30 分钟。
\n\n<?php\nsession_start();\n\n// 设置会话生命周期——30 分钟(1800 秒)\n$timeout_duration = 1800;\n\n// 如果会话中有时间戳且已过期\nif (isset($_SESSION['LAST_ACTIVITY']) && \n (time() - $_SESSION['LAST_ACTIVITY']) > $timeout_duration) {\n // 销毁会话\n session_unset();\n session_destroy();\n // 重定向到登录页面\n header("Location: login.php?timeout=1");\n exit;\n}\n\n// 更新时间戳(如果用户活跃)\n$_SESSION['LAST_ACTIVITY'] = time();\n?>\n\n优点:
\n- \n
- 绝对控制:会话将在不活动 30 分钟后精确过期。 \n
- 不依赖 PHP 设置和垃圾回收器。 \n
- 易于为网站的不同部分更改时间(例如,管理后台 15 分钟,用户 60 分钟)。 \n
缺点:
\n- \n
- 需要在每个页面上添加检查(或将其提取到通用初始化文件中)。 \n
- 如果用户保持页面打开但不点击——会话将过期。对于某些场景(长表单),这是一个缺点。 \n
方法 2. 组合方法:gc_maxlifetime + cookie_lifetime
\n\n如果您不想编写手动检查,可以尝试“开箱即用”地配置 PHP。但请记住其中的细微差别。
\n\n// 在脚本开头,session_start() 之前\n$lifetime = 1800; // 30 分钟\n\n// 设置会话 cookie 的生命周期\nsession_set_cookie_params($lifetime);\n\n// 设置服务器上数据的最大生命周期\nini_set('session.gc_maxlifetime', $lifetime);\n\n// 现在启动会话\nsession_start();\n\n另外——为了让 GC 更频繁地运行,可以减少触发概率:
\n\nini_set('session.gc_probability', 1);\nini_set('session.gc_divisor', 1); // GC 将在每次请求时运行