サーバーサイドアクセス制御 (CORS)

アクセス制御システムはパスワード、個人識別番号 (PIN)、バイオメトリックスキャン、物理的または電子的キーを含むログインクレデンシャルを介してエンティティの認証識別、認証、アクセス承認、およびアカウンタビリティを実行します。

アクセス制御は、コンピューティング環境のリソースを誰が表示したり使用したりすることを規制するために使用できるセキュリティ技術です。

ブラウザは XMLHttpRequest または Fetch API 内から開始されたクロスサイトリクエストに対して特定の HTTPヘッダー を送信します。またクロスサイトレスポンスで特定のHTTPヘッダーが送信されることも期待しています。初回リクエストやサーバからのレスポンスを処理するサンプル JavaScript コードや各ヘッダーの説明など、これらのヘッダーの概要はこの HTTPアクセスコントロール (CORS) の記事に記載されています。この記事では、アクセス制御リクエストの処理と PHP でのアクセス制御レスポンスの策定について説明します。この記事の対象読者は、サーバプログラマまたは管理者です。ここに示すコードサンプルは PHP で書かれていますが ASP.net、Perl、Python、Java などにも同様の概念が適用されます。 一般に、これらの概念は HTTP リクエストを処理し、HTTP レスポンスを動的に作成するサーバ側のプログラミング環境に適用できます。

HTTP ヘッダーの説明

クライアントとサーバーの両方で使用されるHTTPヘッダーを扱う記事はここにあります。

動くコードサンプル

以降のセクションの PHP スニペット (およびサーバーへの JavaScript 呼び出し) は、ここに掲載されている作業コードサンプルから取得されます。これらはクロスサイト XMLHttpRequest を実装するブラウザで動作します。

シンプルな cross-site リクエスト

単純なアクセス制御要求は、次の場合に開始されます。

  • リクエストメソッドとして HTTP/1.1 GET または POST が使用されています。POST の場合、リクエストボディの Content-Typeapplication/x-www-form-urlencodedmultipart/form-data、もしくは text/plain のいずれかです。
  • (X-Modified のような) カスタムヘッダーはHTTPリクエストとともには送信されません。

この場合、いくつかの事項を考慮してレスポンスを返すことができます。

  • 問題のリソースが(GET によってアクセスされる HTTP リソースと同様に)広くアクセスされることになっている場合、リソースに Cookie や HTTP 認証情報などの認証情報が必要でなければ Access-Control-Allow-Origin: * ヘッダを返すことで十分です。
  • 要求者のドメインに基づいてリソースを制限し続ける必要がある場合、またはリソースに資格情報でアクセスする必要がある場合(または資格情報を設定する場合)、リクエストのOrigin ヘッダーでのフィルタリングが必要な場合や、少なくともリクエストの発信元をエコーバック(例: Access-Control-Allow-Origin: http://arunranga.com) する場合があります。さらに、 Access-Control-Allow-Credentials: true のヘッダーを送信する必要があります。これについては後のセクションで説明します。

オリジン間リソース共有 (CORS)のセクションではクライアントとサーバーの間のヘッダー交換を説明します。シンプルリクエストを処理するPHPコードセグメントは次のとおりです。

<?php

// We'll be granting access to only the arunranga.com domain 
// which we think is safe to access this resource as 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>";
}
?>

上記ではブラウザから送信された Origin ヘッダー($_SERVER['HTTP_ORIGIN'] で取得)が 'http://arunranga.com' と一致するかどうかを確認します。一致する場合、Access-Control-Allow-Origin: http://arunranga.comを返します。この例はここで見ることができます。

プリフライトリクエスト

Preflighted Access Control Requests occur when:

  • A method other than GET or POST is used, or if POST is used with a Content-Type other than one of application/x-www-form-urlencoded, multipart/form-data, or text/plain. For instance, if the Content-Type of the POST body is application/xml, a request is preflighted.
  • A custom header (such as X-PINGARUNER) is sent with the request.

The section on Preflighted Access Control Requests shows a header exchange between client and server. A server resource responding to a preflight requests needs to be able to make the following determinations:

Here is an example in PHP of handling a preflighted request:

<?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") {
  // Tell the Client we support invocations from arunranga.com and 
  // that this preflight holds good for only 20 days

  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") {
  // Handle POST by first getting the XML POST blob, 
  // and then doing something to it, and then sending results to the client
 
  if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") {
    $postData = file_get_contents('php://input');
    $document = simplexml_load_string($postData);
    
    // do something with POST data

    $ping = $_SERVER['HTTP_X_PINGARUNER'];
         
    header('Access-Control-Allow-Origin: http://arunranga.com');
    header('Content-Type: text/plain');
    echo // some string response after processing
  } else {
    die("POSTing Only Allowed from arunranga.com");
  }
} else {
    die("No Other Methods Allowed");
}
?>

Note the appropriate headers being sent back in response to the OPTIONS preflight as well as to the POST data. One resource thus handles the preflight as well as the actual request. In the response to the OPTIONS request, the server notifies the client that the actual request can indeed be made with the POST method, and header fields such as X-PINGARUNER can be sent with the actual request. This example can be seen running here.

資格情報のリクエスト

Credentialed Access Control Requests – that is, requests that are accompanied by Cookies or HTTP Authentication information (and which expect Cookies to be sent with responses) – can be either Simple or Preflighted, depending on the request methods used.

In a Simple Request scenario, the request will be sent with Cookies (e.g. if the withCredentials flag is set on XMLHttpRequest). If the server responds with Access-Control-Allow-Credentials: true attached to the credentialed response, then the response is accepted by the client and exposed to web content. In a Preflighted Request, the server can respond with Access-Control-Allow-Credentials: true to the OPTIONS request.

Here is some PHP that handles credentialed requests:

<?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');

  // First See if There Is a 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") {
  // Tell the Client this preflight holds good for only 20 days
  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");
}
?>

Note that in the case of credentialed requests, the Access-Control-Allow-Origin: header must not have a wildcard value of "*".  It must mention a valid origin domain. The example above can be seen running here.

Apache の例

Restrict access to certain URIs

One helpful trick is to use an Apache rewrite, environment variable, and headers to apply Access-Control-Allow-* to certain URIs. This is useful, for example, to constrain cross-origin requests to GET /api(.*).json requests without credentials:

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

関連情報

ドキュメントのタグと貢献者

タグ: 
このページの貢献者: silverskyvicto
最終更新者: silverskyvicto,