aboutsummaryrefslogtreecommitdiff
path: root/files/zh-tw/web/http/server-side_access_control/index.html
blob: ffa7d7eb0e02f540a27f10f42f9658023bf8203f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
---
title: 伺服器端存取控制(CORS)
slug: Web/HTTP/Server-Side_Access_Control
translation_of: Web/HTTP/CORS
---
<p>存取控制系統是執行<a href="http://searchsoftwarequality.techtarget.com/definition/authorization">授權</a>識別、<a href="http://searchsecurity.techtarget.com/definition/authentication">認證</a>、存取核可的實體,也負責透過登入來進行驗證,包含<a href="http://searchsecurity.techtarget.com/definition/password">密碼</a>、個人身份識別碼(personal identification numbers,PINs)、<a href="http://searchsecurity.techtarget.com/definition/biometrics">生物辨識</a>掃描,以及物理或電子的金鑰。</p>

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

<p>{{HTTPSidebar}}</p>

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

<h2 id="HTTP_標頭討論">HTTP 標頭討論</h2>

<p>討論到<a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS">同時涵蓋客戶端及伺服器端使用的 HTTP 標頭的文章在此</a>,建議先閱讀該篇文章。</p>

<h2 id="可執行的程式碼範例">可執行的程式碼範例</h2>

<p>隨後章節的 PHP 程式碼片段(以及 JavaScript 呼叫伺服器)可以在<a class="external" href="http://arunranga.com/examples/access-control/">這裡</a>取得。這些程式在實作了跨網域 {{domxref("XMLHttpRequest")}} 的瀏覽器中都可以運作。</p>

<h2 id="簡單跨網域請求">簡單跨網域請求</h2>

<p><a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS#簡單請求">簡單存取控制請求</a>會在以下情況下被建立發起:</p>

<ul>
 <li>一個 HTTP/1.1 {{HTTPMethod("GET")}}{{HTTPMethod("POST")}} 方法送出之請求。若為 POST,則該請求之 {{HTTPHeader("Content-Type")}} 標頭值須為 <code>application/x-www-form-urlencoded</code><code>multipart/form-data</code><code>text/plain</code> 其中之一。</li>
 <li>HTTP 請求中沒有使用自定義的標頭(如 <code>X-Modified</code> 等等)。</li>
</ul>

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

<ul>
 <li>如果該資源是允許被任何人存取的(就像所有透過 GET 方法存取的 HTTP 資源),則只要回傳帶有 {{HTTPHeader("Access-Control-Allow-Origin")}}<code>: *</code> 標頭值的回應即可。<strong>除非</strong>資源需要身分驗證(credentials),如 <a href="/zh-TW/docs/Web/HTTP/Cookies">Cookies</a> 與 HTTP 認證(Authentication)資訊。</li>
 <li>如果資源應該要限制請求者的網域(domain),<strong>或是</strong>假如資源需要身分驗證(credentials)來進行存取(或是要設定驗證)。則篩選請求的 {{HTTPHeader("Origin")}} 標頭就可能是必要的,或至少呼應請求者的 <code>Origin</code> 標頭值(例如 {{HTTPHeader("Access-Control-Allow-Origin")}}<code>: <a class="external" href="http://arunranga.com" rel="freelink">http://arunranga.com</a></code>)。另外,將會發送 {{HTTPHeader("Access-Control-Allow-Credentials")}}<code>: true</code> 標頭,在<a class="internal" href="#身分憑證請求">下面的章節</a>會進行討論。</li>
</ul>

<p>在 HTTP 存取控制(CORS)一文的<a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS#簡單請求">簡單存取控制請求</a>章節示範了客戶端與伺服器端之間標頭的交流。下面是一個處理簡單請求的 PHP 程式碼片段:</p>

<pre class="brush: php">&lt;?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 "&lt;html&gt;";
  echo "&lt;head&gt;";
  echo "   &lt;title&gt;Another Resource&lt;/title&gt;";
  echo "&lt;/head&gt;";
  echo "&lt;body&gt;",
       "&lt;p&gt;This resource behaves two-fold:";
  echo "&lt;ul&gt;",
         "&lt;li&gt;If accessed from &lt;code&gt;http://arunranga.com&lt;/code&gt; it returns an XML document&lt;/li&gt;";
  echo   "&lt;li&gt;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&lt;/li&gt;",
       "&lt;/ul&gt;",
     "&lt;/body&gt;",
   "&lt;/html&gt;";
}
?&gt;
</pre>

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

<h2 id="預檢請求">預檢請求</h2>

<p><a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS#Preflighted_requests">預檢存取控制請求</a>在以下情況下發起:</p>

<ul>
 <li>使用一個 {{HTTPMethod("GET")}}{{HTTPMethod("POST")}} 以外的 HTTP 方法。或是在使用 {{HTTPMethod("POST")}} 方法的情況下,其 {{HTTPHeader("Content-Type")}} 標頭值為 <code>application/x-www-form-urlencoded</code><code>multipart/form-data</code><code>text/plain</code> <strong>以外</strong>的值。例如,假設使用 <code>POST</code> 方法並包含之 <code>Content-Type</code> 標頭值為 <code>application/xml</code>,則此請求便為預檢(preflighted)請求。</li>
 <li>一個帶有自定義 HTTP 標頭(如 <code>X-PINGARUNER</code>)的請求。</li>
</ul>

<p>在 HTTP 存取控制(CORS)一文的<a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS#預檢請求">預檢存取控制請求</a>章節示範了客戶端與伺服器端之間標頭的交流。一個伺服器資源要回應預檢(preflighted)請求必須能夠進行以下的判斷:</p>

<ul>
 <li>基於 {{HTTPHeader("Origin")}} 的篩選,如果有的話。</li>
 <li>回應一個 {{HTTPMethod("OPTIONS")}} 請求(即預檢(preflighted)請求),包含寄送必要的 {{HTTPHeader("Access-Control-Allow-Methods")}}{{HTTPHeader("Access-Control-Allow-Headers")}} 標頭值(假如有任何應用程式運作所需要的額外標頭),以及若是此資源要求身分驗證,則需要包含 {{HTTPHeader("Access-Control-Allow-Credentials")}} 標頭。</li>
 <li>回應實際(actual)請求,包含處理 <code>POST</code> 請求的資料等等。</li>
</ul>

<p>下面是一個使用 PHP 實作之處理<a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS#預檢請求">預檢請求</a>的範例:</p>

<pre class="brush: php">&lt;?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");
}
?&gt;
</pre>

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

<h2 id="身分驗證請求">身分驗證請求</h2>

<p><a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS#附帶身分憑證的請求">身分驗證存取控制請求</a>——即請求可以附帶 <a href="/zh-TW/docs/Web/HTTP/Cookies">Cookies</a> 或 HTTP 認證(Authentication)訊息(並期望回應攜帶 Cookies)——可以是<a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS#簡單請求">簡單</a><a class="internal" href="/zh-TW/docs/Web/HTTP/Access_control_CORS#預檢">預檢請求</a>,根據請求使用之 HTTP 方法而定。</p>

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

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

<pre class="brush: php">&lt;?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");
}
?&gt;
</pre>

<p>注意此範例中的身分驗證請求,其中的 <code>Access-Control-Allow-Origin:</code> 標頭值<strong>不得</strong>是萬用字元(wildcard)「*」。此標頭值<strong>必須</strong>為一個有效的的來源網域(origin domain)。以上的範例可以在<a class="external" href="http://arunranga.com/examples/access-control/">這裡看到執行的情形</a></p>

<h2 id="Apache_範例">Apache 範例</h2>

<h3 id="限制存取某些_URI">限制存取某些 URI</h3>

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

<pre>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
</pre>

<h2 id="參見">參見</h2>

<ul>
 <li><a class="external" href="http://arunranga.com/examples/access-control/">Examples of Access Control in Action</a></li>
 <li><a href="https://github.com/jackblackevo/cors-jsonp-sample">Client-Side &amp; Server-Side (Java) sample for Cross-Origin Resource Sharing (CORS)</a></li>
 <li><a class="internal" href="/en-US/docs/Web/HTTP/Access_control_CORS">HTTP Access Control covering the HTTP headers</a></li>
 <li>{{domxref("XMLHttpRequest")}}</li>
 <li><a href="/en-US/docs/Web/API/Fetch_API">Fetch API</a></li>
</ul>