Session 是 HTTP 無狀態協議下維持用戶狀態的核心機制。本文系統整理 Session 原理、配置、安全、分散式擴展與現代最佳實務。
PHPSESSID)回傳給瀏覽器session.save_path)<?php
// 必須在輸出任何內容前呼叫
session_start();
// 設定 Session 參數(需在 session_start() 前)
ini_set('session.gc_maxlifetime', 7200); // 2小時過期
ini_set('session.cookie_lifetime', 3600); // Cookie 1小時
ini_set('session.cookie_httponly', 1); // 防 XSS
ini_set('session.cookie_secure', 1); // 僅 HTTPS(生產環境)
ini_set('session.cookie_samesite', 'Lax'); // CSRF 防護
ini_set('session.use_strict_mode', 1); // 防 Session Fixation
// 寫入資料
$_SESSION['user_id'] = 12345;
$_SESSION['username'] = 'john_doe';
$_SESSION['permissions'] = ['read', 'write'];
// 讀取資料
$userId = $_SESSION['user_id'] ?? null;
// 刪除特定鍵
unset($_SESSION['temp_data']);
// 銷毀 Session(登出)
session_unset(); // 清空 $_SESSION
session_destroy(); // 刪除伺服器端檔案
setcookie(session_name(), '', time() - 3600, '/'); // 刪除 Cookie
?>
| 風險 | 防護措施 |
|---|---|
| Session Fixation | session.use_strict_mode=1 + 登入後 session_regenerate_id(true) |
| Session Hijacking | HTTPS 全站 + cookie_secure=1 + cookie_httponly=1 |
| CSRF | cookie_samesite=Lax/Strict + CSRF Token 雙重防護 |
| Session Injection | 勿信任 $_SESSION 來源,序列化資料驗證 |
| 資訊洩露 | 敏感資料加密儲存,定期輪換加密金鑰 |
<?php
// php.ini 或啟動腳本
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379?auth=your_password&database=0&prefix=session:');
// 或使用 phpredis 擴展的 SessionHandler
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$handler = new RedisSessionHandler($redis);
session_set_save_handler($handler, true);
session_start();
?>
<?php
// 建立 Session 表
CREATE TABLE sessions (
id VARCHAR(128) PRIMARY KEY,
data TEXT NOT NULL,
expiry INT UNSIGNED NOT NULL,
INDEX idx_expiry (expiry)
);
class DbSessionHandler implements SessionHandlerInterface {
private PDO $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function open($path, $name): 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() + (int)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 DbSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
?>
適合微服務、Serverless 架構,Session 資料編碼在 Token 中,伺服器無需儲存:
<?php
// 使用 firebase/php-jwt
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$key = 'your-secret-key';
$payload = [
'uid' => 12345,
'permissions' => ['read', 'write'],
'exp' => time() + 3600,
'iat' => time(),
];
$token = JWT::encode($payload, $key, 'HS256');
// 客戶端儲存於 HttpOnly Cookie 或 localStorage
// 驗證
try {
$decoded = JWT::decode($token, new Key($key, 'HS256'));
// $decoded->uid 可用
} catch (Exception $e) {
// Token 無效/過期
}
?>
session_start()session_start(['read_only' => true]) 避免鎖定session.auto_start = 0session.gc_probability = 1, session.gc_divisor = 1000 (0.1% 機率)session.serialize_handler = igbinary (需擴展)| 現象 | 可能原因 | 解決 |
|---|---|---|
| Session 丟失 | Cookie 未送達 / 域名不匹配 | 檢查 session.cookie_domain、session.cookie_path |
| 併發請求阻塞 | Session 檔案鎖定 | 使用 Redis / session_write_close() 提早釋放 |
| 跨子域名共享 | Cookie 預設不跨子域 | session.cookie_domain = '.example.com' |
| 資料截斷 | 序列化長度超限 | 檢查 session.serialize_handler、資料庫欄位長度 |
Session 是狀態管理基石,正確配置安全參數、選擇適合架構的儲存後端,是建立穩健 Web 應用的關鍵。