在 Session 中儲存「最後活動時間戳」,每次請求比對當前時間差,超過閾值則銷毀 Session 並導向登入頁。
<?php
declare(strict_types=1);
function checkSessionTimeout(int $idleLimitSeconds = 1800): void {
// 1. 啟動 Session (若未啟動)
if (session_status() === PHP_SESSION_NONE) {
session_start([
'cookie_httponly' => true,
'cookie_secure' => isset($_SERVER['HTTPS']), // HTTPS 才啟用 Secure
'cookie_samesite' => 'Lax',
'use_strict_mode' => true,
]);
}
// 2. 檢查是否已登入 (假設以 user_id 為準)
if (empty($_SESSION['user_id'])) {
return; // 未登入,不處理
}
$now = time();
$last = $_SESSION['last_activity'] ?? $now;
// 3. 判斷逾時
if ($now - $last > $idleLimitSeconds) {
// 逾時:銷毀 Session
session_unset();
session_destroy();
// 重新啟動乾淨 Session 以儲存訊息
session_start();
$_SESSION['flash_error'] = '登入逾時,請重新登入';
header('Location: /login.php');
exit;
}
// 4. 更新最後活動時間
$_SESSION['last_activity'] = $now;
}
// 在每個受保護頁面頂部引入
require_once 'session_check.php';
checkSessionTimeout(1800); // 30 分鐘閒置逾時
?>
<?php
function checkDualTimeout(int $idle = 1800, int $absolute = 7200): void {
// ... session_start ...
$now = time();
$loginTime = $_SESSION['login_time'] ?? $now;
$lastAct = $_SESSION['last_activity'] ?? $now;
// 絕對逾時:從登入起算,無論是否活動
if ($now - $loginTime > $absolute) {
destroyAndRedirect('登入逾時 (絕對),請重新登入');
}
// 閒置逾時
if ($now - $lastAct > $idle) {
destroyAndRedirect('閒置逾時,請重新登入');
}
$_SESSION['last_activity'] = $now;
}
?>
| 設定 | 推薦值 | 說明 |
|---|---|---|
| session.cookie_httponly | true | 防 JS 竊取 (XSS) |
| session.cookie_secure | true (HTTPS) | 僅 HTTPS 傳輸 |
| session.cookie_samesite | 'Lax' 或 'Strict' | 防 CSRF |
| session.use_strict_mode | true | 拒絕未初始化的 Session ID |
| session.gc_maxlifetime | 1800 (同閒置逾時) | 伺服器端垃圾回收壽命 |
| session.save_path | /var/lib/php/sessions (權限 700, 擁有者 www-data) | 隔離不同應用 Session |
<?php
// php.ini 或執行時設定
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379?auth=password&database=1');
// Redis 內建 TTL 自動過期,無需 gc_maxlifetime
// 閒置逾時邏輯仍需在應用層實作 (更新 last_activity)
?>
Session 逾時核心在於 **伺服器端時間戳比對**,配合安全 Cookie 設定、雙重逾時策略、分散式儲存支援。務必在**每個受保護入口**統一呼叫檢查函式,避免邏輯漏網。