萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP Session 關閉瀏覽器自動銷毀的兩種實作方案

前言

預設 PHP Session 使用「會話 Cookie」(session.cookie_lifetime=0),瀏覽器關閉即失效,但伺服器端 Session 檔案不會立即刪除,需等待垃圾回收 (GC)。本文說明 Session 生命週期機制,並提供兩種「瀏覽器關閉即時銷毀」的實作方案。

Session 生命週期機制

設定預設值說明
session.gc_maxlifetime1440 (秒)伺服器端 Session 資料最大存活時間,超過此時間被視為「垃圾」
session.cookie_lifetime0瀏覽器端 Cookie 存活秒數,0 = 瀏覽器關閉即失效 (會話 Cookie)
session.gc_probability/divisor1/100每 100 次請求有 1 次機率觸發 GC

關鍵觀念:瀏覽器關閉 → 會話 Cookie 消失 → 下次請求送出新 Session ID → 伺服器找不到舊 Session → 視為新會話。但伺服器端舊 Session 檔案仍存在直到 GC 清理。

方案一:縮短 GC 周期 + 短壽命 Cookie (被動式)

適合大多數一般應用,無需前端配合:

<?php
// 在 session_start() 之前設定
ini_set('session.gc_maxlifetime', 300);      // 伺服器端 5 分鐘無活動即可被清理
ini_set('session.cookie_lifetime', 300);     // 瀏覽器端 Cookie 也設 5 分鐘
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 10);           // 提高 GC 機率到 10%

// 或在 php.ini 設定
// session.gc_maxlifetime = 300
// session.cookie_lifetime = 300
// session.gc_probability = 1
// session.gc_divisor = 10

session_start();
// ... 你的程式碼
?>

優缺點分析

  • ✅ 完全伺服器端控制,無需前端 JavaScript
  • ✅ 相容所有瀏覽器、無 JS 環境也生效
  • ⚠️ 非「即時」銷毀,需等待 GC 執行 (機率觸發)
  • ⚠️ 縮短 GC 時間會增加伺服器 I/O 負載
  • ⚠️ 使用者短暫離開 (如切換分頁) 可能導致 Session 遺失

方案二:前端 onunload + AJAX 主動銷毀 (主動式)

瀏覽器關閉/跳轉時發送同步 AJAX 請求銷毀 Session:

前端頁面

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Session 自動銷毀示範</title>
</head>
<body>
    <h1>測試頁面</h1>
    <p>關閉分頁/瀏覽器將觸發 Session 銷毀</p>

    <script>
    // 使用 navigator.sendBeacon (現代、可靠、非阻塞)
    window.addEventListener('unload', function() {
        navigator.sendBeacon('/destroy_session.php');
    });

    // 兼容舊瀏覽器 (同步 AJAX,會阻塞關閉)
    /*
    window.addEventListener('unload', function() {
        var xhr = new XMLHttpRequest();
        xhr.open('POST', '/destroy_session.php', false); // false = 同步
        xhr.send();
    });
    */
    </script>
</body>
</html>

後端銷毀腳本 (destroy_session.php)

<?php
// 僅接受 POST 請求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1. 啟動現有 Session
    session_start();
    
    // 2. 清空所有 Session 變數
    $_SESSION = [];
    
    // 3. 刪除 Session Cookie (瀏覽器端)
    if (ini_get('session.use_cookies')) {
        $params = session_get_cookie_params();
        setcookie(session_name(), '', time() - 42000,
            $params['path'], $params['domain'],
            $params['secure'], $params['httponly']
        );
    }
    
    // 4. 銷毀伺服器端 Session 資料
    session_destroy();
    
    // 可選:回應 204 No Content
    http_response_code(204);
    exit;
}
http_response_code(405); // Method Not Allowed
?>

優缺點分析

  • ✅ 真正「即時」銷毀伺服器端 Session
  • ✅ 精確控制銷毀時機
  • ⚠️ 依賴 JavaScript,無 JS 環境失效
  • ⚠️ unload 事件在某些情況不觸發 (崩潰、強制關閉、行動裝置切換 App)
  • ⚠️ 同步 AJAX 會延遲頁面關閉,影響使用者體驗
  • ⚠️ sendBeacon 不保證伺服器處理完成

方案三:資料庫儲存 Session + 定時清理 (企業級推薦)

<?php
// 使用資料庫儲存 Session,可精確控制每筆 Session 生命週期
// 建表
/*
CREATE TABLE sessions (
    id VARCHAR(128) PRIMARY KEY,
    data TEXT NOT NULL,
    last_activity INT UNSIGNED NOT NULL,
    user_id INT UNSIGNED DEFAULT NULL,
    INDEX idx_last_activity (last_activity),
    INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/

class DatabaseSessionHandler implements SessionHandlerInterface {
    private $pdo;
    public function __construct(PDO $pdo) { $this->pdo = $pdo; }
    
    public function open($savePath, $sessionName) { return true; }
    public function close() { return true; }
    
    public function read($id) {
        $stmt = $this->pdo->prepare("SELECT data FROM sessions WHERE id = ? AND last_activity > ?");
        $stmt->execute([$id, time() - 1440]);
        return $stmt->fetchColumn() ?: '';
    }
    
    public function write($id, $data) {
        $stmt = $this->pdo->prepare("
            INSERT INTO sessions (id, data, last_activity) VALUES (?, ?, ?)
            ON DUPLICATE KEY UPDATE data = VALUES(data), last_activity = VALUES(last_activity)
        ");
        return $stmt->execute([$id, $data, time()]);
    }
    
    public function destroy($id) {
        $stmt = $this->pdo->prepare("DELETE FROM sessions WHERE id = ?");
        return $stmt->execute([$id]);
    }
    
    public function gc($maxLifetime) {
        $stmt = $this->pdo->prepare("DELETE FROM sessions WHERE last_activity < ?");
        return $stmt->execute([time() - $maxLifetime]);
    }
}

// 註冊處理器
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
session_set_save_handler(new DatabaseSessionHandler($pdo), true);
session_start();
?>

最佳實踐建議

  1. 一般網站:方案一 (調整 GC 參數) 即可滿足需求
  2. 高安全性需求 (銀行、後台):方案三 (資料庫 Session) + 短壽命 + 活動檢測
  3. 單頁應用 (SPA):改用 JWT Token,無需伺服器端 Session
  4. 混合方案:一般頁面用方案一,敏感操作頁面 (登出、刪除帳號) 加方案二雙重保險

常見誤區澄清

誤區事實
session_destroy() 會立即刪除 Cookie不會,需手動 setcookie 覆蓋過期時間
unset($_SESSION) 等同 session_destroy()unset 只清空陣列,session_destroy 才刪除伺服器端儲存檔
關閉瀏覽器 Session 立即失效伺服器端資料仍存在直到 GC,瀏覽器重開若帶舊 Cookie 可能復活 (極少見)
session.gc_maxlifetime 精確控制存活GC 是機率觸發,非精確計時器
copyright © 萬盛學電腦網 all rights reserved