HTTP 協議本身是無狀態的,服務器無法識別兩個請求是否來自同一用戶。Session 通過 「服務器存儲數據 + 客戶端持有標識符」 的機制解決此問題。
GET /test.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0...
Accept: */*
HTTP/1.1 200 OK
Date: Fri, 26 Aug 2025 07:44:22 GMT
Server: Apache/2.4.x PHP/8.2.x
X-Powered-By: PHP/8.2.x
Set-Cookie: PHPSESSID=abc123def456; path=/; HttpOnly; SameSite=Lax ← 關鍵:下發 Session ID
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Content-Type: text/html; charset=utf-8
0 ← 輸出內容 (session 計數器初始值)
GET /test.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0...
Accept: */*
Cookie: PHPSESSID=abc123def456 ← 瀏覽器自動攜帶
HTTP/1.1 200 OK
...
Set-Cookie: PHPSESSID=abc123def456; path=/; HttpOnly; SameSite=Lax ← 刷新過期時間
...
Content-Length: 1
1 ← 輸出內容 (計數器遞增)
| 參數 | 默認值 | 說明 | 生產建議 |
|---|---|---|---|
| session.save_handler | files | 存儲引擎: files/redis/memcached/custom | 高併發改用 redis |
| session.save_path | /tmp | 文件存儲路徑 (files 模式) | 專用目錄、權限 700 |
| session.name | PHPSESSID | Cookie 名稱 | 自定義如 APPSESSID |
| session.cookie_lifetime | 0 | Cookie 生命週期 (0=瀏覽器關閉失效) | 設定具體秒數如 3600 |
| session.gc_maxlifetime | 1440 | 服務端數據最大存活秒數 (GC 依據) | 與業務邏輯一致 |
| session.gc_probability | 1 | GC 執行概率分子 | 配合 divisor |
| session.gc_divisor | 1000 | GC 執行概率分母 (1/1000 請求觸發) | 高流量可調大 |
| session.cookie_httponly | On | 禁止 JS 讀取 Cookie (防 XSS) | 必須 On |
| session.cookie_secure | Off | 僅 HTTPS 傳輸 Cookie | HTTPS 環境必須 On |
| session.cookie_samesite | Lax | CSRF 防護: Strict/Lax/None | Lax 平衡體驗安全 |
| session.use_strict_mode | Off | 拒絕未初始化的 Session ID (防固定攻擊) | 建議 On |
sess_ (如 sess_abc123def456)key|s:len:"value";key2|i:123;session.save_path = "2;/var/lib/php/sessions" (創建 2 級子目錄分散文件)。
; php.ini 或運行時 ini_set
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?auth=password&database=0&prefix=session:"
; 選項: weight=1, timeout=2.5, read_timeout=2, retry_interval=10
優勢:內存級速度、原子操作、集群化、TTL 自動過期、支援多實例負載均衡。
class MySqlSessionHandler implements SessionHandlerInterface {
private PDO $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function open($savePath, $sessionName): bool { return true; }
public function close(): bool { return true; }
public function read($id): string|false {
$stmt = $this->pdo->prepare("SELECT data FROM sessions WHERE id = ? AND expiry > ?");
$stmt->execute([$id, time()]);
return $stmt->fetchColumn() ?: '';
}
public function write($id, $data): bool {
$expiry = time() + ini_get('session.gc_maxlifetime');
$stmt = $this->pdo->prepare("
INSERT INTO sessions (id, data, expiry) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE data=VALUES(data), expiry=VALUES(expiry)
");
return $stmt->execute([$id, $data, $expiry]);
}
public function destroy($id): bool {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE id = ?");
return $stmt->execute([$id]);
}
public function gc($maxLifetime): int|false {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE expiry < ?");
$stmt->execute([time()]);
return $stmt->rowCount();
}
}
// 註冊
$handler = new MySqlSessionHandler($pdo);
session_set_save_handler($handler, true); // true = 註冊關閉函數
session_start();
攻擊場景: 惡意用戶誘導受害者使用已知 Session ID (如發送鏈接 ?PHPSESSID=known_id),受害者登錄後,攻擊者用同 ID 劫持會話。
防護措施:
session.use_strict_mode = 1 (PHP 5.5+):拒絕未由服務器生成的 Session ID。session_regenerate_id(true) (參數 true 刪除舊文件)。cookie_secure + cookie_httponly + cookie_samesite。| 函數 | 用途 | 備註 |
|---|---|---|
| session_start() | 啟動/恢復會話 | 必須在輸出前調用 |
| session_regenerate_id(true) | 生成新 ID,可選刪除舊數據 | 登錄/權限變更時必調用 |
| session_id() | 獲取/設置當前 ID | 啟動前調用可指定 ID |
| session_name() | 獲取/設置 Cookie 名 | 啟動前調用 |
| session_destroy() | 銷毀服務端數據 | 不刪除客戶端 Cookie |
| session_unset() | 清空 $_SESSION 變量 | 不銷毀會話文件 |
| session_write_close() | 寫入數據並關閉會話 | 長時間腳本中釋放鎖 |
| session_status() | 獲取狀態 (DISABLED/NONE/ACTIVE) | PHP 5.4+ |
| session_encode() | 序列化 $_SESSION 數據 | 特有格式,非標準 serialize |
| session_decode() | 解碼數據填充 $_SESSION | 配合 encode 使用 |
redis、httponly、secure、strict_mode。user_id、permissions 等核心標識,業務數據走緩存/數據庫。session_write_close() 釋放 Session 鎖,允許並發請求。