| 參數 | 說明 | 推薦值/備註 |
| name | Cookie 名稱 | 唯一標識,避免與框架衝突 |
| value | Cookie 值 | 自動 URL 編碼,建議自行 JSON/序列化 |
| expires | 過期時間 (Unix 時間戳) | 0 = 會話級 (瀏覽器關閉失效),time()+3600*24*30 = 30天 |
| path | 生效路徑 | "/" 全站生效,"/admin/" 僅後台 |
| domain | 生效域名 | ".example.com" 含子域名,"" 當前域名 |
| secure | 僅 HTTPS 傳輸 | 生產環境必須 true |
| httponly | 禁止 JS 訪問 | 防 XSS,必須 true |
| samesite | 跨站請求策略 (PHP 7.3+) | 'Lax'/'Strict'/'None' (None 需 secure=true) |
二、基礎操作封裝類
<?php
class CookieManager {
private string $defaultPath = '/';
private string $defaultDomain = '';
private bool $secure = true; // 僅 HTTPS
private bool $httponly = true;
private string $sameSite = 'Lax';
private ?string $encryptionKey = null; // 可選加密
public function __construct(array $config = []) {
$this->defaultPath = $config['path'] ?? '/';
$this->defaultDomain = $config['domain'] ?? '';
$this->secure = $config['secure'] ?? (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
$this->httponly = $config['httponly'] ?? true;
$this->sameSite = $config['same_site'] ?? 'Lax';
$this->encryptionKey = $config['encryption_key'] ?? null;
}
// 設置 Cookie
public function set(string $name, $value, int $expire = 0, array $options = []): bool {
// 序列化非字串值
if (!is_string($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
}
// 可選加密
if ($this->encryptionKey) {
$value = $this->encrypt($value);
}
$options = array_merge([
'expires' => $expire ?: 0,
'path' => $this->defaultPath,
'domain' => $this->defaultDomain,
'secure' => $this->secure,
'httponly' => $this->httponly,
'samesite' => $this->sameSite,
], $options);
return setcookie($name, $value, $options);
}
// 獲取 Cookie
public function get(string $name, $default = null) {
if (!isset($_COOKIE[$name])) {
return $default;
}
$value = $_COOKIE[$name];
// 解密
if ($this->encryptionKey) {
$value = $this->decrypt($value);
if ($value === false) return $default; // 解密失敗視為無效
}
// 嘗試 JSON 解析
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
return $value;
}
// 刪除 Cookie (設置過期時間為過去)
public function delete(string $name, array $options = []): bool {
$options = array_merge([
'expires' => time() - 3600, // 1小時前
'path' => $this->defaultPath,
'domain' => $this->defaultDomain,
'secure' => $this->secure,
'httponly' => $this->httponly,
'samesite' => $this->sameSite,
], $options);
return setcookie($name, '', $options);
}
// 檢查是否存在
public function has(string $name): bool {
return isset($_COOKIE[$name]);
}
// 簡單加密 (生產環境建議用 openssl 或 libsodium)
private function encrypt(string $data): string {
$iv = random_bytes(16);
$ciphertext = openssl_encrypt($data, 'AES-256-CBC', $this->encryptionKey, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv . $ciphertext);
}
private function decrypt(string $data): string|false {
$data = base64_decode($data);
if ($data === false || strlen($data) < 16) return false;
$iv = substr($data, 0, 16);
$ciphertext = substr($data, 16);
$plaintext = openssl_decrypt($ciphertext, 'AES-256-CBC', $this->encryptionKey, OPENSSL_RAW_DATA, $iv);
return $plaintext === false ? false : $plaintext;
}
}
// 使用範例
$cookie = new CookieManager([
'encryption_key' => 'your-32-byte-secret-key-here!!', // 32 bytes for AES-256
'secure' => true,
'httponly' => true,
'same_site' => 'Strict'
]);
// 設置
$cookie->set('user_prefs', ['theme' => 'dark', 'lang' => 'zh-TW'], time() + 86400 * 30);
// 讀取
$prefs = $cookie->get('user_prefs', ['theme' => 'light', 'lang' => 'en']);
// 刪除
$cookie->delete('user_prefs');
?>
三、原文範例重構與改進
原文 w_cookie 函數問題分析
<?php
// 原文問題:
// 1. 使用未定義的 endes()/deskey (可能是自定義加密)
// 2. 硬編碼過期邏輯
// 3. 參數順序混淆
// 4. 無 HTTPS/httponly/samesite 安全屬性
// 改進版
function setCookieSecure(string $name, $value, int $days = 30, array $options = []): bool {
$defaultOptions = [
'expires' => $days > 0 ? time() + $days * 86400 : 0,
'path' => '/',
'domain' => '',
'secure' => true, // 強制 HTTPS
'httponly' => true, // 防 XSS
'samesite' => 'Lax', // CSRF 保護
];
// 序列化
if (!is_scalar($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
}
return setcookie($name, $value, array_merge($defaultOptions, $options));
}
function deleteCookie(string $name, array $options = []): bool {
return setCookieSecure($name, '', -1, $options); // 負天數 = 過去時間
}
?>
原文 set_cronology 瀏覽歷史記錄重構
<?php
class BrowsingHistory {
private string $cookieName;
private int $maxItems;
private int $ttl; // 秒
public function __construct(string $name = 'browse_history', int $maxItems = 20, int $days = 30) {
$this->cookieName = $name;
$this->maxItems = $maxItems;
$this->ttl = $days * 86400;
}
public function add(string $itemId): void {
$history = $this->get();
// 移除已存在的同項目 (移到最前)
$history = array_values(array_diff($history, [$itemId]));
// 加到開頭
array_unshift($history, $itemId);
// 限制數量
$history = array_slice($history, 0, $this->maxItems);
// 存儲
$this->save($history);
}
public function get(): array {
$cookie = $_COOKIE[$this->cookieName] ?? '[]';
$data = json_decode($cookie, true);
return is_array($data) ? $data : [];
}
private function save(array $history): void {
$value = json_encode($history, JSON_UNESCAPED_UNICODE);
setcookie($this->cookieName, $value, [
'expires' => time() + $this->ttl,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
}
public function clear(): void {
setcookie($this->cookieName, '', [
'expires' => time() - 3600,
'path' => '/',
'secure' => true,
'httponly' => true
]);
}
}
// 使用:記錄用戶瀏覽的商品 ID
$history = new BrowsingHistory('viewed_products', 50, 90);
$history->add($_GET['product_id'] ?? '');
// 獲取歷史顯示
$viewedIds = $history->get();
// 查詢數據庫獲取商品詳情...
?>
四、Cookie 安全最佳實踐
- 始終設置 HttpOnly:防止 XSS 竊取 Cookie
- HTTPS 站點強制 Secure:防止中間人攻擊
- 設置 SameSite:
Lax 平衡體驗與安全,Strict 最高安全,None 跨域需求 (需 Secure)
- 敏感數據加密:Cookie 值可被用戶查看/修改,敏感信息必須加密簽名
- 設置合理過期時間:避免永久 Cookie,定期輪換
- 域名最小化:僅在需要的域名/路徑生效
- 簽名防篡改:HMAC 簽名驗證 Cookie 完整性
簽名防篡改實現
<?php
class SignedCookieManager {
private string $secretKey;
public function __construct(string $secretKey) {
$this->secretKey = $secretKey;
}
public function set(string $name, $value, int $expire = 0): bool {
$payload = is_string($value) ? $value : json_encode($value, JSON_UNESCAPED_UNICODE);
$signature = $this->sign($payload);
$cookieValue = $payload . '.' . $signature;
return setcookie($name, $cookieValue, [
'expires' => $expire ?: 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
}
public function get(string $name, $default = null) {
if (!isset($_COOKIE[$name])) return $default;
$cookieValue = $_COOKIE[$name];
$parts = explode('.', $cookieValue, 2);
if (count($parts) !== 2) return $default;
[$payload, $signature] = $parts;
if (!$this->verify($payload, $signature)) {
// 簽名驗證失敗,可能被篡改
$this->delete($name);
return $default;
}
$decoded = json_decode($payload, true);
return json_last_error() === JSON_ERROR_NONE ? $decoded : $payload;
}
private function sign(string $data): string {
return base64_encode(hash_hmac('sha256', $data, $this->secretKey, true));
}
private function verify(string $data, string $signature): bool {
$expected = $this->sign($data);
return hash_equals($expected, $signature); // 防時序攻擊
}
public function delete(string $name): bool {
return setcookie($name, '', [
'expires' => time() - 3600,
'path' => '/',
'secure' => true,
'httponly' => true
]);
}
}
?>
五、進階應用模式
1. 分佈式 Session (Cookie 存儲 Session ID)
<?php
// PHP 內建 session 使用 Cookie 存儲 session_id
// 自定義 session 存儲 (Redis 集群)
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://redis-cluster:6379');
// Cookie 設置
ini_set('session.cookie_secure', '1');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_samesite', 'Lax');
ini_set('session.use_strict_mode', '1');
ini_set('session.cookie_lifetime', '0'); // 會話級
?>
2. Remember Me 功能 (長期登入 Token)
<?php
function setRememberMeToken(string $userId, string $selector, string $validator): void {
// selector: 用於查找記錄 (可公開)
// validator: 驗證用 (哈希後存儲數據庫)
$token = $selector . ':' . bin2hex(random_bytes(32)); // validator 原文
setcookie('remember_me', $token, [
'expires' => time() + 86400 * 30, // 30天
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
// 數據庫存儲: user_id, selector, validator_hash, expires_at
}
function validateRememberMe(): ?int {
if (!isset($_COOKIE['remember_me'])) return null;
[$selector, $validator] = explode(':', $_COOKIE['remember_me'], 2);
if (!$selector || !$validator) return null;
// 查數據庫: SELECT user_id, validator_hash FROM remember_tokens WHERE selector = ?
// 驗證: hash_equals($storedHash, hash('sha256', $validator))
// 檢查過期時間
// 成功返回 user_id,失敗返回 null
}
?>
3. CSRF Token 雙重提交模式
<?php
function generateCsrfToken(): string {
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
// 同時設置 Cookie (雙重提交)
setcookie('csrf_token', $token, [
'expires' => 0, // 會話級
'path' => '/',
'secure' => true,
'httponly' => true, // JS 讀不到,但表單提交會自動帶上
'samesite' => 'Strict'
]);
return $token;
}
function validateCsrfToken(string $token): bool {
// 檢查 Session
if (!isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $token)) {
return false;
}
// 檢查 Cookie (雙重驗證)
if (!isset($_COOKIE['csrf_token']) || !hash_equals($_COOKIE['csrf_token'], $token)) {
return false;
}
return true;
}
?>
六、調試與常見問題
| 問題 | 原因 | 解決 |
| Cookie 未設置成功 | 輸出內容前調用 setcookie、域名/路徑不匹配、Secure 不匹配 | 確保在任何輸出前調用,檢查域名路徑,HTTPS 站點啟用 Secure |
| 無法刪除 Cookie | 刪除時 path/domain/secure/samesite 與設置時不一致 | 刪除時完全複製原設置參數,僅改 expires 為過去 |
| 子域名無法共享 | domain 設置錯誤 | 設置 domain=".example.com" (帶點前綴) |
| 中文值亂碼 | 自動 URL 編碼/解碼 | 自行 json_encode/urlencode,或確保編碼一致 |
| SameSite None 不生效 | SameSite=None 必須配合 Secure=true | 同時設置 secure=true |
七、測試代碼片段
<?php
// 測試 Cookie 設置與讀取
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
$name = $_POST['name'] ?? 'test_cookie';
$value = $_POST['value'] ?? 'test_value';
$days = (int)($_POST['days'] ?? 1);
if ($action === 'set') {
setCookieSecure($name, $value, $days);
echo "Cookie 設置成功: $name = $value (有效期 $days 天)";
} elseif ($action === 'delete') {
deleteCookie($name);
echo "Cookie 刪除請求已發送: $name";
}
}
// 顯示當前所有 Cookie
echo "當前 Cookie:
";
print_r($_COOKIE);
echo "
";
?>
Cookie 看似簡單,但涉及協議細節、安全屬性、編碼處理、跨域策略等眾多細節。建立統一的管理類、遵循安全最佳實踐、理解瀏覽器行為差異,是開發穩健 Web 應用的基礎。
- 上一篇文章: PHP 圖片 URL 提取與移除域名正則實戰
- 下一篇文章: PHP $_GET 超全域陣列完整教學:表單處理、陣列傳遞與安全實務
Copyright ©
程式碼之家 All Rights Reserved