身為一位資深的 PHP 後端工程師,我來為您詳細解釋什麼是 CORS,以及如何在 PHP 中正確地處理跨域請求。
什麼是 CORS? (Cross-Origin Resource Sharing 跨域資源共享)
CORS 是一種基於 HTTP 標頭的機制,它允許伺服器指示除了其自身來源(網域、協定或端口)之外的任何來源,瀏覽器應該允許從中載入資源。簡而言之,CORS 是瀏覽器實施的同源策略 (Same-Origin Policy) 的一種安全擴展,它提供了一種受控的方式來放寬限制。
同源策略:
這是一個瀏覽器層面的安全機制,它限制了從一個源(例如 https://example.com)載入的網頁,不能與另一個不同源(例如 https://api.example.org 或 http://example.com 或 https://example.com:8080)的資源進行互動。這是為了防止惡意的網站從其他網站竊取用戶數據。
為什麼需要 CORS?
在現代 Web 開發中,前端應用程式(通常運行在一個網域)經常需要呼叫後端 API(可能運行在另一個網域或子網域)。例如:
- 你的前端 React 應用程式運行在
http://localhost:3000
,但後端 PHP API 運行在http://api.yourdomain.com
。 - 你的網站
https://www.yourdomain.com
需要呼叫https://api.yourdomain.com
提供的數據。 - 你使用了第三方服務的 API,例如支付網關或地圖服務。
在沒有 CORS 的情況下,瀏覽器會因為同源策略而阻止這些跨域請求,導致前端無法正常獲取數據,進而在開發中遇到著名的 "CORS policy" 錯誤。CORS 就是為了解決這個問題而設計的。
CORS 的工作原理:
CORS 的核心在於伺服器在 HTTP 回應中發送特殊的 CORS 相關 HTTP 標頭,告知瀏覽器該資源是否允許來自特定來源的請求。瀏覽器會根據這些標頭來判斷是否允許將響應內容暴露給前端 JavaScript。
CORS 請求分為兩種類型:
-
簡單請求 (Simple Requests):
如果一個跨域請求滿足以下所有條件,它被認為是簡單請求:
- 方法為
GET
、HEAD
或POST
。 - 除了由使用者代理自動設置的標頭(如
User-Agent
)和在 Fetch 規範中被定義為 CORS 安全清單的標頭(如Accept
、Accept-Language
、Content-Language
、Content-Type
,但Content-Type
僅限於application/x-www-form-urlencoded
、multipart/form-data
或text/plain
)之外,沒有額外的手動設置標頭。 - 沒有使用
ReadableStream
物件發送請求。
對於簡單請求,瀏覽器會直接發送請求,並在伺服器回應時檢查
Access-Control-Allow-Origin
標頭。 - 方法為
-
預檢請求 (Preflight Requests):
如果一個跨域請求不符合簡單請求的條件(例如,使用了 PUT、DELETE 方法,或者使用了自定義的 Content-Type,或者自定義標頭),瀏覽器會先發送一個 OPTIONS 請求到目標伺服器。這個 OPTIONS 請求被稱為「預檢請求」,它會帶有一些特殊的標頭(如 Access-Control-Request-Method 和 Access-Control-Request-Headers),詢問伺服器是否允許隨後的實際請求。
伺服器接收到 OPTIONS 請求後,會根據其 CORS 配置回應相應的 CORS 標頭。如果伺服器的回應表明允許該實際請求,瀏覽器才會發送真正的請求。如果預檢請求失敗,瀏覽器會直接阻止實際請求。
如何在 PHP 中處理跨域請求?
在 PHP 中處理 CORS 主要是透過在伺服器端設置正確的 HTTP 響應標頭。這些標頭必須在任何內容輸出到瀏覽器之前發送。
以下是一些常見的 CORS 相關 HTTP 標頭及其在 PHP 中的設置方法:
1. 設置 Access-Control-Allow-Origin
(最重要)
這個標頭指定了哪些來源被允許訪問你的資源。
-
允許所有來源(不推薦用於生產環境):
PHP<?php header('Access-Control-Allow-Origin: *'); // 注意:如果設置為 '*',並且同時需要發送 Credentials (如 cookies, HTTP authentication),瀏覽器會阻止請求。 // 因為這會帶來安全風險。 ?>
使用
*
應當極為謹慎,尤其當你的 API 處理敏感數據時。這意味著任何網站都可以向你的 API 發送請求。 -
允許特定來源(推薦):
PHP<?php // 允許來自 'http://localhost:3000' 和 'https://yourfrontend.com' 的請求 $allowedOrigins = ['http://localhost:3000', 'https://yourfrontend.com']; if (isset($_SERVER['HTTP_ORIGIN']) && in_array($_SERVER['HTTP_ORIGIN'], $allowedOrigins)) { header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); } else { // 如果來源不在允許列表中,你可以選擇不發送 CORS 標頭,讓瀏覽器阻止請求 // 或者直接拒絕請求並返回錯誤 // header('HTTP/1.1 403 Forbidden'); // exit('Forbidden'); } ?>
這是最安全和推薦的做法。動態檢查請求的
Origin
標頭,並只允許受信任的來源。
2. 設置 Access-Control-Allow-Methods
這個標頭用於預檢請求 (OPTIONS) 的回應中,告知瀏覽器實際請求允許使用哪些 HTTP 方法。
<?php
// 允許 GET, POST, PUT, DELETE, OPTIONS 方法
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
?>
3. 設置 Access-Control-Allow-Headers
這個標頭也用於預檢請求的回應中,告知瀏覽器實際請求允許攜帶哪些自定義的 HTTP 請求頭。
<?php
// 允許 Content-Type, Authorization, X-Requested-With 等標頭
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
?>
4. 設置 Access-Control-Allow-Credentials
如果你的跨域請求需要攜帶憑證(如 Cookies、HTTP 認證頭或客戶端 SSL 憑證),你需要在伺服器端設置此標頭為 true
。
<?php
header('Access-Control-Allow-Credentials: true');
?>
重要注意事項:
- 當
Access-Control-Allow-Credentials
為true
時,Access-Control-Allow-Origin
不能設置為*
。它必須是一個明確的來源。這是為了安全考量。 - 前端在發送請求時也需要設置
withCredentials = true
(對於XMLHttpRequest
) 或credentials: 'include'
(對於fetch API
)。
5. 設置 Access-Control-Max-Age
這個標頭指示瀏覽器可以緩存預檢請求的結果,在指定的時間內(以秒為單位),瀏覽器不需要再發送預檢請求。這可以減少不必要的網路往返,提高效能。
<?php
// 緩存預檢請求結果 1 天 (86400 秒)
header('Access-Control-Max-Age: 86400');
?>
6. 處理 OPTIONS 預檢請求
當瀏覽器發送預檢請求時,它的 HTTP 方法是 OPTIONS
。你的 PHP 腳本需要特別處理這種請求,只需發送 CORS 相關標頭,然後直接退出,不需要執行實際的業務邏輯。
<?php
// 這是一個通用的 CORS 處理模板
if (isset($_SERVER['HTTP_ORIGIN'])) {
// 您可以將此替換為允許的來源列表,例如:
$allowedOrigins = ['http://localhost:3000', 'https://yourfrontend.com'];
if (in_array($_SERVER['HTTP_ORIGIN'], $allowedOrigins)) {
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
} else {
// 如果來源不允許,可以選擇不回應 CORS 標頭或直接退出
header('HTTP/1.1 403 Forbidden');
exit;
}
} else {
// 可能是非瀏覽器請求,或者沒有 Origin 標頭,根據您的需求處理
// 為了安全,如果沒有 Origin 標頭,通常不應該允許跨域
}
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Allow-Credentials: true'); // 如果需要憑證
header('Access-Control-Max-Age: 86400'); // 緩存預檢請求 1 天
// 如果是 OPTIONS 請求,直接退出,不執行後續業務邏輯
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Content-Length: 0'); // OPTIONS 請求通常沒有響應體
header('Content-Type: text/plain'); // 或者其他適當的 Content-Type
exit;
}
// -----------------------------------------------------------
// 以下是您的實際業務邏輯處理代碼
// -----------------------------------------------------------
// 範例:處理 GET 請求
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
echo json_encode(['message' => 'Hello from PHP API!']);
}
// 範例:處理 POST 請求
else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
echo json_encode(['received_data' => $data, 'status' => 'success']);
}
?>
最佳實踐與注意事項
- 最小權限原則: 永遠只允許最少的必要權限。不要無腦地設置
Access-Control-Allow-Origin: *
,除非你明確知道這對你的應用程式是安全的。 - 框架/函式庫: 如果你使用 PHP 框架(如 Laravel, Symfony, CodeIgniter),它們通常會提供內建的 CORS 配置選項或 Middleware (中介層) 來更方便、更安全地處理 CORS,你應該優先使用這些功能而不是手動設置標頭。例如,Laravel 有
fruitcake/laravel-cors
套件,可以透過配置文件輕鬆管理 CORS 策略。 - 配置 Apache/Nginx: 另一種處理 CORS 的方式是在 Web 伺服器層級(Apache 或 Nginx)配置 CORS 標頭,而不是在每個 PHP 腳本中。這可以讓你的 PHP 程式碼更乾淨,並且在某些情況下可以提高效能。
- Apache (在 .htaccess 或 httpd.conf 中):
Apache
<IfModule mod_headers.c> Header set Access-Control-Allow-Origin "http://localhost:3000" Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With" Header set Access-Control-Allow-Credentials "true" Header set Access-Control-Max-Age "86400" </IfModule>
- Nginx (在 server 或 location 區塊中):
這種方式通常更推薦用於靜態資源或不需要複雜邏輯判斷的 API。Nginxadd_header 'Access-Control-Allow-Origin' 'http://localhost:3000'; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Max-Age' '86400'; if ($request_method = 'OPTIONS') { add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' '0'; return 204; }
- Apache (在 .htaccess 或 httpd.conf 中):
- 安全考慮: CORS 本身並不是安全措施,它只是放鬆了瀏覽器的同源策略。它並不能保護你的伺服器免受未經授權的訪問。你仍然需要實施其他安全措施,例如認證、授權、輸入驗證、防止 SQL Injection 和 XSS 等。
總之,理解 CORS 的工作原理和如何在 PHP 中正確設置相關 HTTP 標頭,對於構建現代 Web 應用程式至關重要。希望這份詳細的說明對您有所幫助!
沒有留言:
張貼留言