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

PHP Session 失效/為空終極排查指南:從配置到代碼的全鏈路分析

     編輯:PHP 綜合知識

現象:驗證碼 Session 為空,登錄失敗

典型場景:$_SESSION['captcha'] 在生成頁面已設置,提交驗證時讀取為空/未定義。

核心檢查清單(按概率排序)

1. session.save_path 目錄權限/不存在(最高頻)

症狀:Session 文件無法寫入,每次請求生成新 Session ID,數據自然丟失。

<?php
// 診斷腳本:放入項目入口執行
echo 'save_path: ' . ini_get('session.save_path') . PHP_EOL;
echo 'session_id: ' . session_id() . PHP_EOL;
echo 'session_status: ' . session_status() . PHP_EOL; // 2=活躍

$path = ini_get('session.save_path');
// 處理 "N;/path" 格式 (分級目錄)
if (strpos($path, ';') !== false) {
    list(, $path) = explode(';', $path, 2);
}
echo '實際路徑: ' . $path . PHP_EOL;
echo '目錄存在: ' . (is_dir($path) ? '是' : '否') . PHP_EOL;
echo '可寫入: ' . (is_writable($path) ? '是' : '否') . PHP_EOL;
echo '擁有者: ' . posix_getpwuid(fileowner($path))['name'] . PHP_EOL;
echo '進程用戶: ' . get_current_user() . PHP_EOL;

// 測試寫入
$testFile = $path . '/sess_test_' . random_int(1000,9999);
if (@file_put_contents($testFile, 'test') !== false) {
    unlink($testFile);
    echo '測試寫入: 成功' . PHP_EOL;
} else {
    echo '測試寫入: 失敗 - 請檢查權限/SELinux/AppArmor' . PHP_EOL;
}
?>

解決:

  • 確保目錄存在:mkdir -p /var/lib/php/sessions && chown www-data:www-data /var/lib/php/sessions && chmod 700 /var/lib/php/sessions
  • Windows:IIS_IUSRS / IUSR 需有修改權限
  • SELinux:chcon -R -t httpd_var_run_t /var/lib/php/sessions
  • 避免使用 /tmp(系統清理任務會刪除)

2. session_start() 調用缺失/位置錯誤

<?php
// ❌ 錯誤:輸出內容後才 start
echo '<html>';
session_start(); // 警告:headers already sent

// ✅ 正確:腳本最開頭,任何輸出前
session_start();
// 設置/讀取 $_SESSION
?><html>...

檢查點:所有涉及 Session 的文件**入口處**必須調用 session_start(),且無 BOM 頭、無空白輸出。

3. Session ID 傳遞中斷(Cookie/URL)

傳遞方式常見故障排查
Cookie (預設)瀏覽器禁用 Cookie、跨域、SameSite=Lax/Strict、域名不匹配F12 Network 看 Set-Cookie/Request Cookie;檢查 session.cookie_domain/session.cookie_samesite
URL 參數 (透明 SID)session.use_trans_sid=Off、重寫規則衝突、表單缺少隱藏域啟用 ini_set('session.use_trans_sid', 1);檢查 output_add_rewrite_var()

關鍵配置:

<?php
// 跨子域共享 Session
ini_set('session.cookie_domain', '.example.com'); // 頂級域名前加點
ini_set('session.cookie_samesite', 'Lax'); // 或 None (需 Secure)
ini_set('session.cookie_secure', true);   // HTTPS 必須
ini_set('session.use_strict_mode', 1);    // 防 Session Fixation
ini_set('session.use_only_cookies', 1);   // 禁用 URL 傳遞 (更安全)
?>

4. 多服務器環境:Session 存儲不共享

症狀:負載均衡下,請求分發到不同節點,Session 文件只在本地磁盤。

解決方案(按推薦度):

  1. Redis 集群 (最主流):session.save_handler = redis, session.save_path = "tcp://redis-cluster:6379?auth=pass&database=0" (需 phpredis 擴展)
  2. 數據庫:自定義 session_set_save_handler() 寫入 MySQL/PostgreSQL
  3. NFS/共享存儲:掛載同一目錄作為 session.save_path(性能差、單點故障,不推薦)
  4. Sticky Session:LB 綁定 Session ID 到固定節點(擴展性差,避免使用)

5. 代碼邏輯導致 Session 被清空/覆蓋

<?php
// ❌ 常見錯誤:直接賦值空數組覆蓋
$_SESSION = []; // 或 unset($_SESSION);

// ✅ 正確銷毀特定鍵
unset($_SESSION['captcha']);

// ✅ 正確銷毀整個 Session
session_unset();     // 清空 $_SESSION
session_destroy();   // 刪除服務端文件 & 發送刪除 Cookie
// 注意:session_destroy() 後 $_SESSION 仍可讀寫,下次請求才生新 ID
?>

6. 子域名/端口差異導致 Cookie 不共享

session.cookie_domain 預設為當前主機名。a.example.com 設置的 Cookie 默認不發送給 b.example.com。

修正:ini_set('session.cookie_domain', '.example.com'); (注意前綴點)。

7. PHP 版本/配置差異

  • session.auto_start = On 會在腳本開始前自動啟動,**禁止**在代碼中再次 session_start() (會報錯)。
  • session.gc_maxlifetime (預設 1440s) 過短導致高併發下文件被 gc 刪除。集群環境需統一配置並由 Redis TTL 控制。
  • session.sid_length/session.sid_bits_per_character 影響 ID 長度與熵,建議設置 32/5 (256bit)。

一鍵診斷腳本(部署到項目根目錄訪問)

<?php
// session_diag.php
header('Content-Type: text/plain; charset=utf-8');
session_start();

echo "=== PHP Session 診斷報告 ===\n";
echo "PHP Version: " . PHP_VERSION . "\n";
echo "SAPI: " . PHP_SAPI . "\n\n";

echo "--- 核心配置 ---\n";
foreach([
    'session.save_handler','session.save_path','session.name','session.auto_start',
    'session.gc_maxlifetime','session.gc_probability','session.gc_divisor',
    'session.cookie_lifetime','session.cookie_path','session.cookie_domain',
    'session.cookie_secure','session.cookie_httponly','session.cookie_samesite',
    'session.use_strict_mode','session.use_only_cookies','session.use_trans_sid',
    'session.sid_length','session.sid_bits_per_character',
] as $k) {
    echo "$k = " . ini_get($k) . "\n";
}

echo "\n--- 運行狀態 ---\n";
echo "session_id: " . session_id() . "\n";
echo "session_status: " . session_status() . " (1=disabled, 2=active, 0=none)\n";
echo "COOKIE[" . session_name() . "]: " . ($_COOKIE[session_name()] ?? '未設置') . "\n";
echo "\$_SESSION 內容:\n";
print_r($_SESSION);

echo "\n--- 寫入測試 ---\n";
$_SESSION['diag_test'] = time();
echo "寫入 diag_test: " . $_SESSION['diag_test'] . "\n";
echo "重新讀取: " . ($_SESSION['diag_test'] ?? '失敗') . "\n";

$savePath = ini_get('session.save_path');
if (strpos($savePath, ';') !== false) $savePath = explode(';', $savePath, 2)[1];
echo "\n--- 文件系統 ---\n";
echo "save_path 目錄: $savePath\n";
echo "存在: " . (is_dir($savePath) ? '是' : '否') . "\n";
echo "可寫: " . (is_writable($savePath) ? '是' : '否') . "\n";
$files = glob($savePath . '/sess_*');
echo "Session 文件數: " . count($files) . "\n";
if ($files) {
    $latest = max(array_map('filemtime', $files));
    echo "最新修改: " . date('Y-m-d H:i:s', $latest) . "\n";
}
?>

驗證碼專項修復清單

  1. 生成頁 (captcha.php):session_start() → $_SESSION['captcha'] = $code → 輸出圖片,**無任何其他輸出**。
  2. 表單頁:session_start() → `` (防緩存)。
  3. 驗證頁:session_start() → 取 $_SESSION['captcha'] → **大小寫不敏感比對** → 驗證後 unset($_SESSION['captcha']) (防重放)。
  4. 檢查 session.cookie_path 是否覆蓋三個頁面路徑 (預設 / 通常無問題)。
  5. 若部署在子目錄,確保 session.cookie_path 設置為父路徑或 /。

終極建議: 生產環境**棄用文件存儲**,直接接入 Redis Session,解決 99% 併發、分布式、權限、清理問題。Laravel/Symfony/ThinkPHP 均內置支持,原生 PHP 僅需安裝 phpredis 並修改兩行 ini 配置。

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