Cookie 是伺服器要求瀏覽器在本地儲存的小型鍵值對,隨後續請求自動攜帶。PHP 提供 setcookie()/setrawcookie() 介面操作 Cookie。本文涵蓋完整參數、安全屬性、刪除技巧、子域共享、實戰「記住我」登入流程與現代替代方案。
setcookie(
string $name,
string $value = "",
int $expires_or_options = 0,
string $path = "",
string $domain = "",
bool $secure = false,
bool $httponly = false
): bool
// PHP 7.3+ 支援陣列選項
setcookie($name, $value, [
'expires' => time() + 3600,
'path' => '/',
'domain' => '.example.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax', // PHP 7.3+
]);
| 參數 | 說明 | 建議值 |
|---|---|---|
| name | Cookie 名稱(不可含 ; , = 空白) | 英文底線 |
| value | 值(自動 urlencode,setrawcookie 不編碼) | 結構化資料請 json_encode |
| expires | Unix 時間戳,0 或省略 = 瀏覽器關閉失效(Session Cookie) | 記住我:time()+30*86400 |
| path | 生效路徑,/ 為全站 | / |
| domain | 生效域名,.example.com 含子域 | .example.com |
| secure | 僅 HTTPS 傳輸 | HTTPS 站必開 |
| httponly | JS 無法讀取 (document.cookie) | 1 (防 XSS) |
| samesite | 跨站請求攜帶策略:Strict/Lax/None | Lax (平衡安全與體驗) |
time() - 3600, 'path' => '/', 'domain' => '.example.com', 'secure' => true, 'httponly' => true]);
document.cookie 讀不到)。Strict:完全禁止跨站攜帶(連結跳轉也不帶)。Lax:允許安全跨站(GET 連結、預載),預設推薦。None:需 Secure,允許所有跨站(第三方 iframe、嵌入服務)。CREATE TABLE users (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
remember_token VARCHAR(64) DEFAULT NULL,
token_expires INT UNSIGNED DEFAULT NULL
);
prepare('SELECT id, password_hash FROM users WHERE username = ?');
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
// 正常 Session 登入
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
// 記住我:產生隨機 Token 存資料庫 + 寫 Cookie
if ($remember) {
$token = bin2hex(random_bytes(32));
$expires = time() + 30 * 86400; // 30 天
$pdo->prepare('UPDATE users SET remember_token=?, token_expires=? WHERE id=?')
->execute([$token, $expires, $user['id']]);
setcookie('remember_token', $token, [
'expires' => $expires,
'path' => '/',
'domain' => '.example.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
}
header('Location: /dashboard');
exit;
}
$error = '帳號或密碼錯誤';
}
// ... 顯示登入表單
prepare('SELECT id FROM users WHERE remember_token = ? AND token_expires > ?');
$stmt->execute([$token, time()]);
if ($user = $stmt->fetch()) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
// 可選:滑動延長 Token 有效期
$newExp = time() + 30 * 86400;
$pdo->prepare('UPDATE users SET token_expires = ? WHERE id = ?')->execute([$newExp, $user['id']]);
setcookie('remember_token', $token, ['expires' => $newExp, 'path' => '/', 'domain' => '.example.com', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
} else {
// Token 無效/過期,清除 Cookie
setcookie('remember_token', '', ['expires' => time() - 3600, 'path' => '/', 'domain' => '.example.com', 'secure' => true, 'httponly' => true]);
}
}
prepare('UPDATE users SET remember_token=NULL, token_expires=NULL WHERE id=?')->execute([$userId]);
}
$_SESSION = [];
session_destroy();
setcookie('remember_token', '', ['expires' => time() - 3600, 'path' => '/', 'domain' => '.example.com', 'secure' => true, 'httponly' => true]);
header('Location: /login');
time() + 3600,
'path' => '/',
'domain' => '.example.com', // 關鍵:前綴點號
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
注意:session.cookie_domain 同理設定 .example.com 即可跨子域共享 Session。
| 現象 | 原因 | 解決 |
|---|---|---|
| setcookie() 回傳 true 但瀏覽器無 Cookie | 輸出已開始(空白、BOM、echo)、headers_sent() 為 true | 確保 setcookie() 在任何輸出前、檔案無 BOM、ob_start() |
| Cookie 值被截斷/異常 | 值含 ; , = 空白、未 urlencode | json_encode() 結構化資料、setrawcookie() 自行編碼 |
| 跨子域不共享 | domain 未設 .example.com、SameSite=Strict 阻擋 | 設 domain、改 Lax |
| 刪除無效 | path/domain/secure/httponly 不完全一致 | 刪除時參數與設定時完全一致 |
| 開發環境 HTTPS 無 Cookie | Secure=1 但本地無 HTTPS | 本地用 lvh.me + mkcert、或開發環境關 Secure |
將 JWT 簽名後的 Token 存入 HttpOnly; Secure; SameSite=Lax Cookie,而非 LocalStorage,兼具無狀態擴充性與 XSS 防護。伺服器端僅驗證簽名,無需 Session 儲存。
Cookie 是瀏覽器端狀態的基石。掌握 完整參數、安全三屬性、刪除對稱性、子域共享、記住我 Token 輪換,並建立「敏感 Cookie 必 HttpOnly+Secure+SameSite」的鐵律,即能在便利與安全間取得平衡。架構演進期可平滑遷移至 JWT Cookie 模式。