在開發 API 時,如果希望允許特定網域,如 wingzero.tw  及其所有子網域(如 api.wingzero.twadmin.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:允許 GETPOST,並允許 OPTIONS 預檢請求。

  • Access-Control-Allow-Headers: Content-Type, Authorization:允許 Content-TypeAuthorization 等標頭,確保 API 可以處理 JSON 及身份驗證。

  • Access-Control-Allow-Credentials: true:允許跨域請求攜帶憑證(如 cookiesAuthorization 標頭)。

透過這種方式,我們能夠讓特定網域及其所有子網域存取 API,並且符合瀏覽器的 CORS 安全規則。這種方法可以確保 API 安全性,同時避免 CORS 相關錯誤,提高跨域請求的兼容性!