在開發 API 時,如果希望允許特定網域,如 wingzero.tw 及其所有子網域(如 api.wingzero.tw、admin.wingzero.tw)存取 API,就需要設定 CORS(跨來源資源共享,Cross-Origin Resource Sharing)。然而,瀏覽器的 CORS 機制不允許 Access-Control-Allow-Origin 使用萬用字 * 來匹配部分子網域,因此我們需要手動處理。
1. 問題點
直接使用 header("Access-Control-Allow-Origin: *.wingzero.tw"); 這種寫法是不被瀏覽器接受的,因為 Access-Control-Allow-Origin 不能包含萬用字 * 來匹配特定網域。因此,我們需要手動檢查請求的 Origin,並在回應時動態設定 Access-Control-Allow-Origin。
2. 解決方案:動態設定 CORS
我們可以在 CI_Controller 的 __construct 方法中處理 CORS 設定,確保允許 wingzero.tw 及其所有子網域。
程式碼範例
defined('BASEPATH') or exit('No direct script access allowed');
class Email extends CI_Controller
{
public function __construct()
{
parent::__construct();
// 允許所有 *.wingzero.tw 的網域
if (isset($_SERVER['HTTP_ORIGIN'])) {
$origin = $_SERVER['HTTP_ORIGIN'];
if (preg_match('/^https?:\/\/([a-zA-Z0-9-]+\.)?wingzero\.tw$/', $origin)) {
header("Access-Control-Allow-Origin: $origin");
}
}
// 允許必要的 CORS 設定
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Credentials: true");
}
public function send()
{
// 處理 OPTIONS 預檢請求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
$data = [
"stats" => "ok"
];
// return json
$this->output
->set_content_type('application/json')
->set_output(json_encode($data));
}
}
3. 程式碼解析
(1) 動態設定 Access-Control-Allow-Origin
-
$_SERVER['HTTP_ORIGIN']會取得發送 API 請求的來源網址(如https://api.wingzero.tw)。 -
使用
preg_match('/^https?:\/\/([a-zA-Z0-9-]+\.)?wingzero\.tw$/', $origin)來檢查請求是否來自wingzero.tw或其子網域。 -
如果符合,則將
Access-Control-Allow-Origin設為該Origin,確保瀏覽器允許請求。
(2) 處理 OPTIONS 預檢請求(Preflight Request)
當 API 接受 POST 或有特殊標頭(如 Authorization)時,瀏覽器會先發送 OPTIONS 預檢請求來確認伺服器是否允許該請求。
-
若請求方法為
OPTIONS,則直接exit;,避免影響後續請求。
(3) 允許的 HTTP 方法與標頭
-
Access-Control-Allow-Methods: GET, POST, OPTIONS:允許GET、POST,並允許OPTIONS預檢請求。 -
Access-Control-Allow-Headers: Content-Type, Authorization:允許Content-Type和Authorization等標頭,確保 API 可以處理 JSON 及身份驗證。 -
Access-Control-Allow-Credentials: true:允許跨域請求攜帶憑證(如cookies或Authorization標頭)。
透過這種方式,我們能夠讓特定網域及其所有子網域存取 API,並且符合瀏覽器的 CORS 安全規則。這種方法可以確保 API 安全性,同時避免 CORS 相關錯誤,提高跨域請求的兼容性!