HTTP 基礎認證 是 HTTP 協議內建的最簡單認證機制:瀏覽器彈出原生對話框輸入用戶名/密碼,以 Authorization: Basic base64(user:pass) 頭發送。PHP 通過 $_SERVER['PHP_AUTH_USER'] / ['PHP_AUTH_PW'] 獲取憑證。雖簡單,**但存在明文傳輸、無登出機制、CSRF 風險、無法自定義登錄頁**等缺陷。本文將系統講解實現細節、安全加固、適用場景及現代替代方案。
<?php
// auth_guard.php - 引入即可保護任意頁面
function requireHttpBasicAuth(string $realm = 'Restricted Area', array $validUsers = []): void {
// 1. 檢查是否已提供憑證
if (!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
challenge($realm);
}
$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
// 2. 驗證憑證 (示例:硬編碼/配置文件/數據庫/哈希比對)
if (!validateCredentials($user, $pass, $validUsers)) {
challenge($realm, '用戶名或密碼錯誤');
}
// 3. 通過:可選設置全局用戶標識
$GLOBALS['CURRENT_AUTH_USER'] = $user;
// 註冊關閉連接時清理 (可選)
// register_shutdown_function(fn() => unset($GLOBALS['CURRENT_AUTH_USER']));
}
function validateCredentials(string $user, string $pass, array $validUsers): bool {
// 方案 A: 硬編碼/配置數組 ['user' => 'hash']
if (isset($validUsers[$user])) {
// 推薦:存儲 password_hash() 生成的哈希,驗證用 password_verify()
return password_verify($pass, $validUsers[$user]);
}
// 方案 B: 數據庫查詢
// $stmt = $pdo->prepare("SELECT password_hash FROM users WHERE username=? AND status=1");
// $stmt->execute([$user]);
// $hash = $stmt->fetchColumn();
// return $hash && password_verify($pass, $hash);
return false;
}
function challenge(string $realm, string $message = 'Authentication Required'): never {
// 關鍵 Header:觸發瀏覽器彈窗
header('WWW-Authenticate: Basic realm="' . $realm . '"');
header('HTTP/1.0 401 Unauthorized');
// 可選:輸出友好錯誤頁 (瀏覽器取消彈窗時顯示)
exit("401 Unauthorized
$message
Realm: $realm
");
}
// === 使用示例 ===
// 1. 定義合法用戶 (生產環境從配置/數據庫讀取,密碼存哈希)
$users = [
'admin' => password_hash('securePass123', PASSWORD_DEFAULT),
'api_user' => password_hash('apiKey456', PASSWORD_DEFAULT),
];
// 2. 頁面最頂部引入 (任何輸出前)
requireHttpBasicAuth('Admin Panel', $users);
// 3. 後續代碼只有通過認證才會執行
echo "歡迎, " . htmlspecialchars($GLOBALS['CURRENT_AUTH_USER']) . "!";
// 敏感操作...
?>
WWW-Authenticate: Basic realm="My Realm":**Basic 首字母大寫**,**realm 值必須雙引號包裹**,**不允許單引號**。HTTP/1.0 401 Unauthorized:**HTTP 版本與狀態碼之間只能有一個空格**。ob_start() 輸出緩衝。PHP_AUTH_* 變量不可用。cgi.rfc2616_headers = 0 (php.ini 默認值)。| 缺陷 | 風險 | 加固措施 |
|---|---|---|
| 明文傳輸 (Base64 非加密) | 中間人竊取賬號密碼 | 強制 HTTPS (HSTS),絕不在 HTTP 下使用 |
| 無登出機制 | 用戶無法主動登出,瀏覽器關閉前憑證長期有效 | 無完美方案。變通:修改 realm 強制重新認證、JS 調用 fetch('/logout', {credentials: 'include'}) 返回 401 (部分瀏覽器支援) |
| CSRF 風險 | 瀏覽器自動攜帶憑證發送請求,惡意頁面可偽造請求 | 關鍵操作**額外驗證 CSRF Token** 或要求再次輸入密碼/雙因子 |
| 暴力破解 | 無限制嘗試 | IP 限流 (如 5 次/分鐘)、賬號鎖定、Fail2ban、雙因子認證 (2FA) |
| 密碼存儲 | 明文/弱哈希洩露 | **僅存 password_hash() (bcrypt/argon2) 哈希**,驗證用 password_verify() |
| Realm 信息洩露 | 洩露系統內部命名 | 使用通用 Realm 名稱 (如 "Restricted Area") |
Digest 認證不傳輸明文密碼,而是傳輸 MD5(username:realm:password) 等響應值,**比 Basic 稍安全 (但仍防不了中間人重放/降級攻擊,現代建議仍首選 HTTPS + Token/JWT)**。PHP 5.1+ 支援 $_SERVER['PHP_AUTH_DIGEST'],解析驗證較複雜,建議使用成熟庫 (如 symfony/security-http)。
| 方案 | 核心機制 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| Session + Cookie (傳統 Web) | 服務端 Session ID 存 Cookie | 成熟、支援登出、CSRF 保護成熟、可存儲用戶狀態 | 有狀態、需 Session 共享存儲 (集群)、CSRF 風險需防範 | 傳統服務端渲染應用、管理後台 |
| JWT (JSON Web Token) | 無狀態 Token (Header.Payload.Signature) | 無狀態、跨域友好、可攜帶權限聲明、適合微服務/前後端分離 | 無法主動失效 (需黑名單/短過期+刷新機制)、Payload 可見、簽名算法選擇關鍵 | API 認證、微服務、單頁應用 (SPA)、移動端 |
| OAuth 2.0 / OpenID Connect | 授權碼模式 + ID Token / Access Token | 行業標準、支援第三方登錄、細粒度授權、刷新機制完善 | 流程複雜、需授權服務器實現/接入 | 開放平台、SSO、第三方應用授權 |
| API Key / Bearer Token | Header: Authorization: Bearer <token> | 簡單、無狀態、易撤銷、適合服務間調用 | 傳輸需 HTTPS、無內置權限結構 (需自建) | 內部服務調用、對外開放 API |
| HTTP Basic Auth | Header: Authorization: Basic <base64> | 極簡、無需 Cookie/Session、瀏覽器原生支援、適合內網/臨時/機器對機器 | 明文傳輸、無登出、CSRF、無自定義 UI、安全性弱 | **內網開發/測試環境、簡單機器對機器、臨時保護靜態資源、Prometheus/Grafana 等工具內建支援** |
ob_start() 防止誤輸出導致 Header 失效。PHP HTTP 基礎認證通過 $_SERVER['PHP_AUTH_USER/PW'] 實現**極簡門禁**,核心在於正確發送 WWW-Authenticate 和 401 Header。但其**明文傳輸、無登出、CSRF 風險、無自定義 UI** 等缺陷決定了它**不適合面向用戶的生產環境主流認證**。現代應用應採用 **HTTPS + JWT / Session / OAuth 2.0**。Basic Auth 的正確定位:**內網開發測試環境快速加鎖、機器對機器簡單認證 (如監控採集端)、兼容舊設備/工具 (Prometheus, Grafana, 某些路由器/NAS 管理界面)**。若必須在生產用於用戶認證,**務必強制 HTTPS、存哈希、加限流、加 2FA、明確告知用戶無登出功能**。