PHP Session 預設透過 Cookie 傳遞 Session ID (預設名稱 PHPSESSID)。當使用者禁用 Cookie,Session 將失效。本文提供三種無 Cookie 環境下維持 Session 的方案,並分析優缺點與安全性。
session.use_trans_sid (自動 URL 重寫)PHP 可自動在所有相對連結、表單 action 中加入 PHPSESSID=... 參數。
; php.ini
session.use_trans_sid = 1
; 或運行時
ini_set('session.use_trans_sid', '1');
session_start();
開發者自行在連結、表單中加入 Session ID。
<?php
session_start();
$sid = session_id();
?>
<a href="page2.php?sid=<?= htmlspecialchars($sid) ?>">下一頁</a>
<?php
// page2.php
session_id($_GET['sid'] ?? ''); // 必須在 session_start() 之前
session_start();
// 現在 $_SESSION 可用
<form method="post" action="process.php">
<input type="hidden" name="sid" value="<?= htmlspecialchars(session_id()) ?>">
<!-- 其他欄位 -->
</form>
將 Session ID 儲存在資料庫/Redis/檔案,透過**唯一辨識碼** (如使用者 ID、API Token、簽名 URL) 關聯,而非直接暴露 Session ID。
CREATE TABLE user_sessions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
session_id VARCHAR(255) NOT NULL, -- PHP 原生 Session ID
token VARCHAR(64) NOT NULL UNIQUE, -- 對外暴露的安全 Token
user_agent VARCHAR(255),
ip_address VARBINARY(16), -- INET6_ATON()
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
INDEX idx_token (token),
INDEX idx_user (user_id)
);
<?php
class SessionManager {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
// 建立安全 Token 關聯
public function createSession(int $userId, string $userAgent, string $ip): string {
session_start();
$phpSid = session_id();
$token = bin2hex(random_bytes(32)); // 256-bit 安全 Token
$stmt = $this->pdo->prepare("
INSERT INTO user_sessions (user_id, session_id, token, user_agent, ip_address, expires_at)
VALUES (?, ?, ?, ?, INET6_ATON(?), DATE_ADD(NOW(), INTERVAL 2 HOUR))
");
$stmt->execute([$userId, $phpSid, $token, $userAgent, $ip]);
return $token; // 回傳給前端,存於 localStorage / Header / URL
}
// 依 Token 還原 Session
public function resumeByToken(string $token): bool {
$stmt = $this->pdo->prepare("
SELECT session_id FROM user_sessions
WHERE token = ? AND expires_at > NOW()
");
$stmt->execute([$token]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
return false;
}
session_id($row['session_id']);
session_start();
return true;
}
// 驗證並延長有效期
public function validateAndExtend(string $token): bool {
$stmt = $this->pdo->prepare("
UPDATE user_sessions
SET expires_at = DATE_ADD(NOW(), INTERVAL 2 HOUR)
WHERE token = ? AND expires_at > NOW()
");
$stmt->execute([$token]);
return $stmt->rowCount() > 0;
}
}
// 使用流程
// 1. 登入成功後
$token = $mgr->createSession($userId, $_SERVER['HTTP_USER_AGENT'], $_SERVER['REMOTE_ADDR']);
// 回傳 token 給前端 (JSON、設定 Cookie、或 URL 參數)
// 2. 後續請求 (API / AJAX / 無 Cookie 頁面)
// 前端在 Header 傳遞: Authorization: Bearer <token>
// 或 URL: ?token=xxx
$token = $_GET['token'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if ($mgr->resumeByToken($token)) {
// $_SESSION 可用
$mgr->validateAndExtend($token);
} else {
// Token 無效/過期,要求重新登入
}
| 方案 | 實作難度 | 安全性 | 適用場景 | 維護成本 |
|---|---|---|---|---|
| use_trans_sid | 低 | ⭐ (極低) | 舊系統快速相容 | 低 |
| 手動 URL/表單 | 中 | ⭐⭐ (低) | 小型專案、特定流程 | 高 |
| Token 關聯 (推薦) | 中高 | ⭐⭐⭐⭐⭐ (高) | 現代 Web App、API、SPA、無 Cookie 環境 | 中 (封裝後低) |
DELETE FROM user_sessions WHERE expires_at < NOW()。禁用 Cookie 並非 Session 死胡同,但**直接暴露 Session ID 於 URL 是危險的反模式**。現代應用應採用 **不透明 Token + 伺服器端映射** 架構,兼顧相容性與安全性。將邏輯封裝為 SessionManager 服務,即可在既有專案平滑導入。