PHP Session 應用詳解:從歷史輔助庫到現代原生最佳實踐
Session 是 HTTP 無狀態協議下維持用戶狀態的核心機制。本文回顧 PHP3 時代 phplib 實作、PHP4 原生函數演進,並給出 PHP 7/8 時代的完整安全配置、自定義儲存處理器 (Redis/Database) 與常見坑點指南。
歷史回顧
- PHP3:無內建 Session,依賴
phplib (page_open/page_close, DB 存儲)。
- PHP4:引入
session_start(), session_register() (已棄用), $_SESSION 超全域陣列 (PHP 4.1+)。預設檔案儲存 /tmp。
- PHP 5.4+:
session_register() 移除,強制使用 $_SESSION['key'] = $val。
- PHP 7+:
session_start(['read_and_close' => true])、session_gc()、session_create_id()、session_set_save_handler() 物件導向介面 SessionHandlerInterface。
現代基礎用法 (PHP 7/8)
<?php
// 1. 最開頭啟動 (輸出任何內容前)
session_start([
'cookie_lifetime' => 3600, // 1 小時
'cookie_secure' => true, // 僅 HTTPS
'cookie_httponly' => true, // 防 XSS 竊取
'cookie_samesite' => 'Lax', // CSRF 防護
'use_strict_mode' => true, // 拒絕未初始化的 Session ID
'sid_length' => 48, // ID 長度 (更難猜測)
'sid_bits_per_character' => 6, // 字元熵
]);
// 2. 寫入
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'john';
$_SESSION['permissions'] = ['read', 'write'];
// 3. 讀取
$userId = $_SESSION['user_id'] ?? null;
// 4. 刪除單一鍵
unset($_SESSION['temp_data']);
// 5. 銷毀整個 Session (登出)
session_unset(); // 清空 $_SESSION
session_destroy(); // 刪除伺服器端存儲 & 發送刪除 Cookie
?>
php.ini 關鍵安全配置 (生產環境建議值)
| 指令 | 建議值 | 說明 |
| session.use_strict_mode | 1 | 拒絕客戶端提供的未初始化 SID,防 Session Fixation |
| session.cookie_secure | 1 | 僅 HTTPS 傳輸 Cookie |
| session.cookie_httponly | 1 | JS 無法讀取 document.cookie |
| session.cookie_samesite | Lax 或 Strict | 防 CSRF,Lax 允許導航連結攜帶 |
| session.gc_maxlifetime | 1440 (視業務) | 垃圾回收最大壽命 (秒) |
| session.gc_probability / gc_divisor | 1 / 100 | 每 100 次請求觸發 1 次 GC |
| session.save_handler | files / redis / memcached | 儲存引擎 |
| session.save_path | /var/lib/php/sessions (或 redis://...) | 儲存路徑,需 0700 權限、屬 www-data |
| session.sid_length | 48 | Session ID 字元長度 |
| session.sid_bits_per_character | 6 | 每字元位元數 (6 = a-zA-Z0-9,-) |
自定義 Session 儲存處理器:Redis 範例 (高併發、分散式、共享 Session)
<?php
class RedisSessionHandler implements SessionHandlerInterface {
private \Redis $redis;
private string $prefix = 'sess:';
private int $ttl = 1800; // 30 分鐘
public function __construct(\Redis $redis, string $prefix = 'sess:', int $ttl = 1800) {
$this->redis = $redis;
$this->prefix = $prefix;
$this->ttl = $ttl;
}
public function open($savePath, $sessionName): bool { return true; }
public function close(): bool { return true; }
public function read($sessionId): string|false {
$data = $this->redis->get($this->prefix . $sessionId);
return $data ?? '';
}
public function write($sessionId, $data): bool {
return $this->redis->setex($this->prefix . $sessionId, $this->ttl, $data);
}
public function destroy($sessionId): bool {
return (bool)$this->redis->del($this->prefix . $sessionId);
}
public function gc($maxLifetime): int|false {
// Redis 靠 TTL 自動過期,無需掃描
return true;
}
}
// 註冊
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$handler = new RedisSessionHandler($redis);
session_set_save_handler($handler, true); // true = 註冊 shutdown function
session_start();
?>
資料庫儲存處理器 (MySQL) 核心 SQL
CREATE TABLE `sessions` (
`sess_id` VARCHAR(128) NOT NULL PRIMARY KEY,
`data` BLOB NOT NULL,
`expiry` INT UNSIGNED NOT NULL,
`last_activity` INT UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- write: INSERT ... ON DUPLICATE KEY UPDATE data=VALUES(data), expiry=VALUES(expiry), last_activity=VALUES(last_activity)
-- read: SELECT data FROM sessions WHERE sess_id=? AND expiry > UNIX_TIMESTAMP()
-- destroy: DELETE FROM sessions WHERE sess_id=?
-- gc: DELETE FROM sessions WHERE expiry <= UNIX_TIMESTAMP()
常見坑點與解決
- Headers already sent:
session_start() 前有輸出 (空白、BOM、echo)。解法:ob_start() 或調整代碼順序。
- Session 資料不見:多台伺服器用檔案儲存未共享 → 改用 Redis/DB;
session.gc_maxlifetime 過短;子域名未共享 Cookie (session_set_cookie_params(0, '/', '.example.com'))。
- 並發鎖死:預設檔案處理器對同一 Session 串行鎖定。長時間請求 (如大檔下載) 會阻塞同用戶其他請求。解法:
session_write_close() 盡早釋放鎖,或用 Redis (無鎖/樂觀鎖)。
- Session Fixation:攻擊者固定 SID 誘導受害者登入。防禦:
use_strict_mode=1、登入後 session_regenerate_id(true)。
- 序列化格式:
session.serialize_handler (php/igbinary/msgpack)。igbinary/msgpack 體積更小、速度更快,需安裝擴充。
分散式系統 Session 共享架構圖
[Client] <-- Cookie: PHPSESSID --> [Load Balancer]
|
+--------------------+--------------------+
| | |
[PHP-FPM #1] [PHP-FPM #2] [PHP-FPM #3]
| | |
+--------------------+--------------------+
|
[Redis Cluster / MySQL]
(Session Storage)
總結
Session 從 phplib 到原生函數再到 SessionHandlerInterface,核心挑戰始終是安全性 (防竊取/固定/注入)、可擴展性 (分散式共享)、效能 (鎖爭用、GC)。現代專案:預設用 Redis + 嚴格模式 + 安全 Cookie 屬性,極少自寫處理器,直接用成熟框架 (Laravel/Symfony) 整合的 Session 元件。
copyright ©
萬盛學電腦網 all rights reserved