--- title: 伺服器端存取控制(CORS) slug: Web/HTTP/Server-Side_Access_Control translation_of: Web/HTTP/CORS ---

存取控制系統是執行授權識別、認證、存取核可的實體,也負責透過登入來進行驗證,包含密碼、個人身份識別碼(personal identification numbers,PINs)、生物辨識掃描,以及物理或電子的金鑰。

存取控制是用於規範計算環境之資源可供哪些人或單位觀看、使用的一種安全技術。

{{HTTPSidebar}}

瀏覽器透過 {{domxref("XMLHttpRequest")}} 或 Fetch API 所發起的跨網域請求(cross-site request),會在寄送時使用特定的 HTTP 標頭。同樣的,由伺服器回傳的跨網域回應(cross-site response)中也能看到特定的 HTTP 標頭。關於這些特定標頭的簡介,包括使用 JavaScript 發起請求與處理來自伺服器回應的範例以及每一個標頭的討論,可以在 HTTP 存取控制(CORS)一文中找到,並應該搭配本文一起閱讀。這篇文章包含使用 PHP 處理存取控制請求與建立存取控制回應。本文的目標讀者為伺服器端程式設計師或管理員。僅管本篇範例使用的是 PHP,但類似的概念也適用於 ASP.net、Perl、Python、Java 等等其他語言;一般來說,這些概念也能套用在任何處理 HTTP 請求及動態建立 HTTP 回應的伺服器端程式環境中。

HTTP 標頭討論

討論到同時涵蓋客戶端及伺服器端使用的 HTTP 標頭的文章在此,建議先閱讀該篇文章。

可執行的程式碼範例

隨後章節的 PHP 程式碼片段(以及 JavaScript 呼叫伺服器)可以在這裡取得。這些程式在實作了跨網域 {{domxref("XMLHttpRequest")}} 的瀏覽器中都可以運作。

簡單跨網域請求

簡單存取控制請求會在以下情況下被建立發起:

在此情況下,回傳回應需要考慮以下條件:

在 HTTP 存取控制(CORS)一文的簡單存取控制請求章節示範了客戶端與伺服器端之間標頭的交流。下面是一個處理簡單請求的 PHP 程式碼片段:

<?php

// 我們僅授權讓 arunranga.com 網域來存取資源
// 因為我們認為該網域存取本 application/xml 資源是安全的

if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") {
    header('Access-Control-Allow-Origin: http://arunranga.com');
    header('Content-type: application/xml');
    readfile('arunerDotNetResource.xml');
} else {
  header('Content-Type: text/html');
  echo "<html>";
  echo "<head>";
  echo "   <title>Another Resource</title>";
  echo "</head>";
  echo "<body>",
       "<p>This resource behaves two-fold:";
  echo "<ul>",
         "<li>If accessed from <code>http://arunranga.com</code> it returns an XML document</li>";
  echo   "<li>If accessed from any other origin including from simply typing in the URL into the browser's address bar,";
  echo   "you get this HTML document</li>",
       "</ul>",
     "</body>",
   "</html>";
}
?>

以上的程式會檢查由瀏覽器所送出之請求的 {{HTTPHeader("Origin")}} 標頭(透過取得 $_SERVER['HTTP_ORIGIN'])是否為「http://arunranga.com」。若相符,則回傳之回應中加入 {{HTTPHeader("Access-Control-Allow-Origin")}}: http://arunranga.com 標頭值。這個範例可以在這裡看到執行的情形

預檢請求

預檢存取控制請求在以下情況下發起:

在 HTTP 存取控制(CORS)一文的預檢存取控制請求章節示範了客戶端與伺服器端之間標頭的交流。一個伺服器資源要回應預檢(preflighted)請求必須能夠進行以下的判斷:

下面是一個使用 PHP 實作之處理預檢請求的範例:

<?php

if($_SERVER['REQUEST_METHOD'] == "GET") {

  header('Content-Type: text/plain');
  echo "This HTTP resource is designed to handle POSTed XML input";
  echo "from arunranga.com and not be retrieved with GET";

} elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS") {
  // 告訴客戶端我們支援來自 arunranga.com 的呼叫
  // 以及這個預檢請求的有效期限僅有 20 天

  if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") {
    header('Access-Control-Allow-Origin: http://arunranga.com');
    header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
    header('Access-Control-Allow-Headers: X-PINGARUNER');
    header('Access-Control-Max-Age: 1728000');
    header("Content-Length: 0");
    header("Content-Type: text/plain");
    //exit(0);
  } else {
    header("HTTP/1.1 403 Access Forbidden");
    header("Content-Type: text/plain");
    echo "You cannot repeat this request";
  }

} elseif($_SERVER['REQUEST_METHOD'] == "POST") {
  // 處理 POST 請求,第一步為取得 POST 請求中 blob 型態的 XML
  // 並且對其做一些處理,再傳送結果給客戶端

  if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") {
    $postData = file_get_contents('php://input');
    $document = simplexml_load_string($postData);

    // 對 POST 請求的資料做一些處理

    $ping = $_SERVER['HTTP_X_PINGARUNER'];

    header('Access-Control-Allow-Origin: http://arunranga.com');
    header('Content-Type: text/plain');
    echo // 在處理之後要回傳的一些回應字串
  } else {
    die("POSTing Only Allowed from arunranga.com");
  }
} else {
    die("No Other Methods Allowed");
}
?>

注意範例中在回應 {{HTTPMethod("OPTIONS")}} 預檢(preflighted)請求與回應 {{HTTPMethod("POST")}} 請求時都會回傳相對應的 HTTP 標頭值,因此一個伺服器資源可以處理預檢以及實際(actual)請求。在回應 OPTIONS 預檢請求之回應標頭中,伺服器告知客戶端可以使用 POST 方法發送實際(actual)請求,並且能於實際(actual)請求的 HTTP 標頭欄位中帶上 X-PINGARUNER 及其值。這個範例可以在這裡看到執行的情形

身分驗證請求

身分驗證存取控制請求——即請求可以附帶 Cookies 或 HTTP 認證(Authentication)訊息(並期望回應攜帶 Cookies)——可以是簡單預檢請求,根據請求使用之 HTTP 方法而定。

簡單請求情境中,請求將會連同 Cookies 一起發送(例如當 withCredentials 旗標被設置於 {{domxref("XMLHttpRequest")}} 時)。假如伺服器以附帶了 {{HTTPHeader("Access-Control-Allow-Credentials")}}: true 標頭值的身分驗證回應來回傳,則回應會被客戶端接受並且可被用於網頁內容中。在預檢請求中,伺服器可以用 Access-Control-Allow-Credentials: true 標頭來回應 OPTIONS 預檢請求。

以下為一些處理身分驗證請求的 PHP 程式片段:

<?php

if($_SERVER['REQUEST_METHOD'] == "GET") {
  header('Access-Control-Allow-Origin: http://arunranga.com');
  header('Access-Control-Allow-Credentials: true');
  header('Cache-Control: no-cache');
  header('Pragma: no-cache');
  header('Content-Type: text/plain');

  // 檢查有沒有 Cookie,若沒有則當作是第一次訪問
  if (!isset($_COOKIE["pageAccess"])) {
    setcookie("pageAccess", 1, time()+2592000);
    echo 'I do not know you or anyone like you so I am going to';
    echo 'mark you with a Cookie :-)';
  } else {
    $accesses = $_COOKIE['pageAccess'];
    setcookie('pageAccess', ++$accesses, time()+2592000);
    echo 'Hello -- I know you or something a lot like you!';
    echo 'You have been to ', $_SERVER['SERVER_NAME'], ';
    echo 'at least ', $accesses-1, ' time(s) before!';
  }
} elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS") {
  // 告訴客戶端這個預檢請求的有效期限僅有 20 天
  if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") {
    header('Access-Control-Allow-Origin: http://arunranga.com');
    header('Access-Control-Allow-Methods: GET, OPTIONS');
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 1728000');
    header("Content-Length: 0");
    header("Content-Type: text/plain");
  } else {
    header("HTTP/1.1 403 Access Forbidden");
    header("Content-Type: text/plain");
    echo "You cannot repeat this request";
  }
} else {
  die("This HTTP Resource can ONLY be accessed with GET or OPTIONS");
}
?>

注意此範例中的身分驗證請求,其中的 Access-Control-Allow-Origin: 標頭值不得是萬用字元(wildcard)「*」。此標頭值必須為一個有效的的來源網域(origin domain)。以上的範例可以在這裡看到執行的情形

Apache 範例

限制存取某些 URI

一個實用的訣竅是使用 Apache rewrite 環境變數(environment variable),並且讓 HTTP 標頭套用 Access-Control-Allow-* 至某些 URI。這相當有用,例如要限制跨來源(cross-origin)請求 GET /api(.*).json 為不帶身分驗證的請求:

RewriteRule ^/api(.*)\.json$ /api$1.json [CORS=True]
Header set Access-Control-Allow-Origin "*" env=CORS
Header set Access-Control-Allow-Methods "GET" env=CORS
Header set Access-Control-Allow-Credentials "false" env=CORS

參見