首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP Cookie 設置、刪除、加密與進階應用完整指南

     編輯:PHP 相關知識

PHP Cookie 完整操作指南:設置、刪除、加密、安全與進階模式

Cookie 是 HTTP 協議中客戶端狀態管理的核心機制。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(string $name, string $value = "", array $options = []): bool

參數詳細說明

二、基礎操作封裝類

<?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 安全最佳實踐

  1. 始終設置 HttpOnly:防止 XSS 竊取 Cookie
  2. HTTPS 站點強制 Secure:防止中間人攻擊
  3. 設置 SameSite:Lax 平衡體驗與安全,Strict 最高安全,None 跨域需求 (需 Secure)
  4. 敏感數據加密:Cookie 值可被用戶查看/修改,敏感信息必須加密簽名
  5. 設置合理過期時間:避免永久 Cookie,定期輪換
  6. 域名最小化:僅在需要的域名/路徑生效
  7. 簽名防篡改: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;
}
?>

六、調試與常見問題

參數說明推薦值/備註
nameCookie 名稱唯一標識,避免與框架衝突
valueCookie 值自動 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)
問題原因解決
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 應用的基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved