Cookie 計數器是追蹤單一瀏覽器造訪次數的經典範例。雖原理簡單,但生產環境需考量:同站多頁面共享、防竄改、GDPR 合規、效能與安全性。本文從最小可行實作進階至生產級方案。
<?php
// counter.php - 必須在任何輸出之前
if (!empty($_COOKIE['visit_count'])) {
$count = (int)$_COOKIE['visit_count'] + 1;
} else {
$count = 1;
}
// 15 小時過期 (54000 秒)
setcookie('visit_count', $count, [
'expires' => time() + 54000,
'path' => '/',
'secure' => true, // 僅 HTTPS
'httponly' => true, // 阻止 JS 存取
'samesite' => 'Lax', // CSRF 防護
]);
?>
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>造訪計數</title></head>
<body>
<p>歡迎光臨!這是您第 <strong><?= $count ?></strong> 次造訪。</p>
</body>
</html>
| 屬性 | 用途 | 建議值 |
|---|---|---|
secure | 僅 HTTPS 傳輸 | true (正式環境) |
httponly | 禁止 JavaScript 讀取 (防 XSS 竊取) | true |
samesite | 跨站請求攜帶策略 | Lax 或 Strict |
path | 生效路徑 | / (全站) |
domain | 生效網域 | 留空 (當前網域) 或 .example.com (含子網域) |
使用者可修改 Cookie 值,若計數用於積點、抽獎等業務邏輯,需簽名驗證。
<?php
const COOKIE_NAME = 'visit_count';
const SECRET_KEY = 'your-32-char-secret-key-here'; // 生產環境從環境變數讀取
function sign(int $value): string {
return hash_hmac('sha256', (string)$value, SECRET_KEY);
}
function verify(string $cookie): ?int {
[$value, $sig] = explode('.', $cookie, 2) + [null, null];
if ($value === null || $sig === null) return null;
if (!hash_equals(sign((int)$value), $sig)) return null;
return (int)$value;
}
// 讀取並驗證
$count = 1;
if (isset($_COOKIE[COOKIE_NAME])) {
$verified = verify($_COOKIE[COOKIE_NAME]);
if ($verified !== null) {
$count = $verified + 1;
}
}
// 寫入簽名後的值
$signed = $count . '.' . sign($count);
setcookie(COOKIE_NAME, $signed, [
'expires' => time() + 86400 * 30, // 30 天
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
?>
<p>第 <?= $count ?> 次造訪</p>
若 Cookie 存放使用者 ID、權限等敏感資訊,建議使用 AEAD 加密 (PHP 7.1+ sodium 擴展)。
$key = sodium_hex2bin('your-64-hex-char-key'); // 32 bytes
function encryptCookie(array $data, string $key): string {
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$plaintext = json_encode($data);
$ciphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
$plaintext, '', $nonce, $key
);
return base64_encode($nonce . $ciphertext);
}
function decryptCookie(string $cookie, string $key): ?array {
$raw = base64_decode($cookie, true);
if ($raw === false) return null;
$nonceLen = SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES;
$nonce = substr($raw, 0, $nonceLen);
$ciphertext = substr($raw, $nonceLen);
$plaintext = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
$ciphertext, '', $nonce, $key
);
return $plaintext ? json_decode($plaintext, true) : null;
}
expires 不宜過長:一般分析性 Cookie 建議 30 天內。setcookie(..., ['expires' => time()-3600]))。| 現象 | 可能原因 | 解決 |
|---|---|---|
| 計數永遠是 1 | Cookie 未寫入 / domain/path 不符 | 檢查 Set-Cookie 標頭、瀏覽器開發者工具 Application 分頁 |
| HTTPS 站點 Cookie 不送出 | 未設 secure=true | 正式環境務必啟用 |
| 子網域無法共享 | domain 未設為 .example.com | 設定 domain => '.example.com' |
| 計數異常暴增 | 爬蟲/預渲染/多頁面重複計數 | 排除 User-Agent、改用 Session 或加入 CSRF token 驗證 |
Cookie 計數器看似簡單,實則牽涉 HTTP 協議、瀏覽器安全模型、隱私法規與密碼學。生產環境請務必啟用 Secure/HttpOnly/SameSite,涉及業務邏輯時加上簽名或加密,並遵循隱私法規設計同意與撤銷機制。