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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
|
---
title: 跨來源資源共用(CORS)
slug: Web/HTTP/CORS
tags:
- AJAX
- CORS
- Cross-Origin Resource Sharing
- Fetch
- Fetch API
- HTTP
- HTTP Access Controls
- Same-origin policy
- Security
- XMLHttpRequest
translation_of: Web/HTTP/CORS
---
<div>{{HTTPSidebar}}</div>
<p><span class="seoSummary">跨來源資源共用(Cross-Origin Resource Sharing ({{Glossary("CORS")}}))是一種使用額外 {{Glossary("HTTP")}} 標頭令目前瀏覽網站的{{Glossary("user agent","使用者代理")}}取得存取其他來源(網域)伺服器特定資源權限的機制。</span>當使用者代理請求一個不是目前文件來源——例如來自於不同網域(domain)、通訊協定(protocol)或通訊埠(port)的資源時,會建立一個<strong>跨來源 HTTP 請求(cross-origin HTTP request)</strong>。</p>
<p>舉個跨來源請求的例子:<code>http://domain-a.com</code> HTML 頁面裡面一個 <a href="/zh-TW/docs/Web/HTML/Element/Img#Attributes"><code><img></code> 標籤的 <code>src</code> 屬性</a>載入來自 <code>http://domain-b.com/image.jpg</code> 的圖片。現今網路上許多頁面所載入的資源,如 CSS 樣式表、圖片影像、以及指令碼(script)都來自與所在位置分離的網域,如內容傳遞網路(content delivery networks, CDN)。</p>
<p>基於安全性考量,程式碼所發出的跨來源 HTTP 請求會受到限制。例如,{{domxref("XMLHttpRequest")}} 及 {{domxref("Fetch_API", "Fetch")}} 都遵守<a href="/zh-TW/docs/Web/Security/Same-origin_policy">同源政策(same-origin policy)</a>。這代表網路應用程式所使用的 API 除非使用 CORS 標頭,否則只能請求與應用程式相同網域的 HTTP 資源。</p>
<p><img alt="" src="https://mdn.mozillademos.org/files/14295/CORS_principle.png" style="height: 305px; width: 440px;"></p>
<p>跨來源資源共用(Cross-Origin Resource Sharing,簡稱 {{Glossary("CORS")}})機制提供了網頁伺服器跨網域的存取控制,增加跨網域資料傳輸的安全性。現代瀏覽器支援在 API 容器(如 {{domxref("XMLHttpRequest")}} 或 {{domxref("Fetch_API", "Fetch")}})中使用 CORS 以降低跨來源 HTTP 請求的風險。</p>
<h2 id="誰應該閱讀這篇文章?">誰應該閱讀這篇文章?</h2>
<p>認真講,所有人。</p>
<p>進一步來說,本文內容主要和網站管理員、伺服器端開發者和前端網頁開發者有關。現代瀏覽器會處理客戶端的跨來源共用元件,包括標頭和政策施定。關於伺服器部分請參閱<a href="/zh-TW/docs/Web/HTTP/Server-Side_Access_Control">跨來源</a>共用<a href="/zh-TW/docs/Web/HTTP/Server-Side_Access_Control">:從伺服器觀點出發(以 PHP 為範例)</a>的補充說明。</p>
<h2 id="哪些請求會使用_CORS?">哪些請求會使用 CORS?</h2>
<p><a href="http://www.w3.org/TR/cors/">跨來源資源</a>共用<a href="http://www.w3.org/TR/cors/">標準</a>可用來開啟以下跨站 HTTP 請求:</p>
<ul>
<li>使用 <code>XMLHttpRequest</code> 或 <code>Fetch API</code> 進行跨站請求,如前所述。</li>
<li>網頁字體(跨網域 CSS 的 <code>@font-face</code> 的字體用途),<a href="http://www.webfonts.info/wiki/index.php?title=%40font-face_support_in_Firefox">所以伺服器可以佈署 TrueType 字體,並限制只讓信任的網站跨站載入</a>。</li>
<li><a href="/zh-TW/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL">WebGL 紋理</a>。</li>
<li>以 <code><a href="/zh-TW/docs/Web/API/CanvasRenderingContext2D/drawImage">drawImage</a></code> 繪製到 Canvas 畫布上的圖形/影片之影格。</li>
<li>CSS 樣式表(讓 <a href="/zh-TW/docs/Web/CSS/CSSOM_View">CSSOM</a> 存取)。</li>
<li>指令碼(for unmuted exceptions)。</li>
</ul>
<p>本文主要討論跨來源資源共用與相關必要的 HTTP 標頭。</p>
<h2 id="功能總覽">功能總覽</h2>
<p>跨來源資源共用標準的運作方式是藉由加入新的 <a href="/zh-TW/docs/Web/HTTP/Headers">HTTP 標頭</a>讓伺服器能夠描述來源資訊以提供予瀏覽器讀取。另外,針對會造成副作用的 HTTP 請求方法(特別是 {{HTTPMethod("GET")}} 以外的 HTTP 方法,或搭配某些 <a href="/zh-TW/docs/Web/HTTP/Basics_of_HTTP/MIME_types">MIME types</a> 的 {{HTTPMethod("POST")}} 方法),規範要求瀏覽器必須要請求傳送「預檢(preflight)」請求,以 HTTP 的 {{HTTPMethod("OPTIONS")}} 方法之請求從伺服器取得其支援的方法。當伺服器許可後,再傳送 HTTP 請求方法送出實際的請求。伺服器也可以通知客戶端是否要連同安全性資料(包括 <a href="/zh-TW/docs/Web/HTTP/Cookies">Cookies</a> 和 HTTP 認證(Authentication)資料)一併隨請求送出。</p>
<p>之後的小節,我們將討論使用情境和相關的 HTTP 標頭。</p>
<h2 id="存取控制情境範例">存取控制情境範例</h2>
<p>我們將在此展示三種情境,來說明跨來源資源共用如何運作。所有的範例都使用 {{domxref("XMLHttpRequest")}} 物件,<code>XMLHttpRequest</code> 可以讓任何支援的瀏覽器進行跨站請求。</p>
<p>本節的 JavaScript 程式碼片段(以及處理跨站請求的伺服器端程式運作實體)可以在 <a class="external external-icon" href="http://arunranga.com/examples/access-control/">http://arunranga.com/examples/access-control/</a> 看到,並可以運行在支援跨站 {{domxref("XMLHttpRequest")}} 請求的瀏覽器上。</p>
<p>對於伺服器端的跨來源資源共用討論(包含 PHP 範例)可參考<a href="/zh-TW/docs/Web/HTTP/Server-Side_Access_Control">伺服器端存取控制</a>。</p>
<h3 id="簡單請求">簡單請求</h3>
<p>部分請求不會觸發 <a href="#Preflighted_requests">CORS 預檢</a>。這類請求在本文中被稱作「簡單請求(simple requests)」,雖然 {{SpecName('Fetch')}} 規範(其定義了 CORS)中並不使用這個述語。一個不觸發 <a href="#Preflighted_requests">CORS 預檢</a>的請求——所謂的「簡單請求(simple requests)」——其滿足以下所有條件:</p>
<ul>
<li>僅允許下列 HTTP 方法:
<ul>
<li>{{HTTPMethod("GET")}}</li>
<li>{{HTTPMethod("HEAD")}}</li>
<li>{{HTTPMethod("POST")}}</li>
</ul>
</li>
<li>除了 user agent 自動設定的標頭(例如 {{HTTPHeader("Connection")}}、{{HTTPHeader("User-Agent")}},或是<a href="https://fetch.spec.whatwg.org/#forbidden-header-name">任何請求規範[Fetch spec]中定義的「禁止使用的標頭名稱[forbidden header name]」</a>中的標頭)之外,僅可手動設定<a href="https://fetch.spec.whatwg.org/#cors-safelisted-request-header">這些於請求規格(Fetch spec)中定義為「CORS 安全列表請求標頭(CORS-safelisted request-header)」</a>的標頭,它們為:
<ul>
<li>{{HTTPHeader("Accept")}}</li>
<li>{{HTTPHeader("Accept-Language")}}</li>
<li>{{HTTPHeader("Content-Language")}}</li>
<li>{{HTTPHeader("Content-Type")}}(但請注意下方的額外要求)</li>
<li>{{HTTPHeader("Last-Event-ID")}}</li>
<li><code><a href="http://httpwg.org/http-extensions/client-hints.html#dpr">DPR</a></code></li>
<li><code><a href="http://httpwg.org/http-extensions/client-hints.html#save-data">Save-Data</a></code></li>
<li><code><a href="http://httpwg.org/http-extensions/client-hints.html#viewport-width">Viewport-Width</a></code></li>
<li><code><a href="http://httpwg.org/http-extensions/client-hints.html#width">Width</a></code></li>
</ul>
</li>
<li>僅允許以下 {{HTTPHeader("Content-Type")}} 標頭值:
<ul>
<li><code>application/x-www-form-urlencoded</code></li>
<li><code>multipart/form-data</code></li>
<li><code>text/plain</code></li>
</ul>
</li>
<li>沒有事件監聽器被註冊到任何用來發出請求的 {{domxref("XMLHttpRequestUpload")}} 物件(經由 {{domxref("XMLHttpRequest.upload")}} 屬性取得)上。</li>
<li>請求中沒有 {{domxref("ReadableStream")}} 物件被用於上傳。</li>
</ul>
<div class="note"><strong>備註:</strong>雖然這些都是網頁目前已經可以送出的跨站請求,但除非伺服器回傳適當標頭,否則不會有資料回傳,因此不允許跨站請求的網站無須擔心會受到新的 HTTP 存取控制影響。</div>
<div class="note"><strong>備註:</strong>WebKit Nightly 與 Safari Technology Preview 對 {{HTTPHeader("Accept")}}、{{HTTPHeader("Accept-Language")}} 及 {{HTTPHeader("Content-Language")}} 標頭值加入了額外的限制。假如這三個標頭中有任一個擁有「非標準」值,WebKit/Safari 就不會將該請求視為「簡單請求」。WebKit/Safari 並沒有於文件中定義何者為「非標準」值,只有在以下 WebKit bugs 中討論:<a href="https://bugs.webkit.org/show_bug.cgi?id=165178" rel="nofollow noreferrer">Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language</a>、<a href="https://bugs.webkit.org/show_bug.cgi?id=165566" rel="nofollow noreferrer">Allow commas in Accept, Accept-Language, and Content-Language request headers for simple CORS</a> 和 <a href="https://bugs.webkit.org/show_bug.cgi?id=166363" rel="nofollow noreferrer">Switch to a blacklist model for restricted Accept headers in simple CORS requests</a>。其它的瀏覽器沒有實作這些額外的限制,因為這並不是規範中的一部分。</div>
<p>例如,假設 <code class="plain">http://foo.example</code> 網域上的網頁內容想要呼叫 <code class="plain">http://bar.other</code> 網域內的內容,以下程式碼可能會在 foo.example 上執行:</p>
<pre class="brush: js" id="line1">var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/public-data/';
function callOtherDomain() {
if(invocation) {
invocation.open('GET', url, true);
invocation.onreadystatechange = handler;
invocation.send();
}
}
</pre>
<p>這將會在客戶端與伺服器端之間發起一個簡單的資料交換,並使用 CORS 相關標頭來處理權限:</p>
<p><img alt="" src="https://mdn.mozillademos.org/files/14293/simple_req.png" style="height: 224px; width: 521px;"></p>
<p>我們來看看這個例子中瀏覽器將會送出什麼到伺服器,而伺服器又會如何回應:</p>
<pre class="brush: shell;highlight:[10,16]">GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
</pre>
<p>第 1 - 10 行是送出的標頭。第 10 行之主要 HTTP 請求標頭中的 {{HTTPHeader("Origin")}} 標頭,它標示出請求是來自 <code class="plain">http://foo.example</code> 網域上的內容。</p>
<p>第 13 - 22 行是 <code class="plain">http://bar.other</code> 網域伺服器回傳的 HTTP 回應。第 16 行伺服器回傳了一個 {{HTTPHeader("Access-Control-Allow-Origin")}} 標頭,從 {{HTTPHeader("Origin")}} 標頭與 {{HTTPHeader("Access-Control-Allow-Origin")}} 標頭中可以看到存取控制協定最簡單的用途。這個例子中,伺服器回傳 <code>Access-Control-Allow-Origin: *</code> 表示允許<strong>任何</strong>網域跨站存取資源,倘若 <code class="plain">http://bar.other</code> 的資源擁有者只准許來自 <code class="plain">http://foo.example</code> 的存取資源請求,那麼將會回傳:</p>
<p><code class="plain">Access-Control-Allow-Origin: http://foo.example</code></p>
<p>如此一來,來源並非 <code class="plain">http://foo.example</code> 網域(由第 10 行請求標頭中的 ORIGIN 標頭確認)便無法以跨站的方式存取資源。<code>Access-Control-Allow-Origin</code> 標頭必須包含請求當中的 <code>Origin</code> 標頭值。</p>
<h3 id="預檢請求">預檢請求</h3>
<p>不同於上面討論「<a href="#簡單請求">簡單請求</a>」的例子,「預檢(preflighted)」請求會先以 HTTP 的 OPTIONS 方法送出請求到另一個網域,確認後續實際(actual)請求是否可安全送出,由於跨站請求可能會攜帶使用者資料,所以要先進行預檢請求。</p>
<p>準確來說,如果滿足以下<strong>任一項條件</strong>時會發出預檢請求:</p>
<ul>
<li><strong>假如</strong>請求方法為以下其中之一:
<ul>
<li>{{HTTPMethod("PUT")}}</li>
<li>{{HTTPMethod("DELETE")}}</li>
<li>{{HTTPMethod("CONNECT")}}</li>
<li>{{HTTPMethod("OPTIONS")}}</li>
<li>{{HTTPMethod("TRACE")}}</li>
<li>{{HTTPMethod("PATCH")}}</li>
</ul>
</li>
<li><strong>或是假如</strong>除了 user agent 自動設定的標頭(例如 {{HTTPHeader("Connection")}}、{{HTTPHeader("User-Agent")}},或是<a href="https://fetch.spec.whatwg.org/#forbidden-header-name">任何請求規範[Fetch spec]中定義的「禁止使用的標頭名稱[forbidden header name]」</a>中的標頭)之外,請求中包含了任何除了<a href="https://fetch.spec.whatwg.org/#cors-safelisted-request-header">這些於請求規格(Fetch spec)中定義為「CORS 安全列表請求標頭(CORS-safelisted request-header)」</a>以外的標頭,具體如下:
<ul>
<li>{{HTTPHeader("Accept")}}</li>
<li>{{HTTPHeader("Accept-Language")}}</li>
<li>{{HTTPHeader("Content-Language")}}</li>
<li>{{HTTPHeader("Content-Type")}}(但請注意下方的額外要求)</li>
<li>{{HTTPHeader("Last-Event-ID")}}</li>
<li><code><a href="http://httpwg.org/http-extensions/client-hints.html#dpr">DPR</a></code></li>
<li><code><a href="http://httpwg.org/http-extensions/client-hints.html#save-data">Save-Data</a></code></li>
<li><code><a href="http://httpwg.org/http-extensions/client-hints.html#viewport-width">Viewport-Width</a></code></li>
<li><code><a href="http://httpwg.org/http-extensions/client-hints.html#width">Width</a></code></li>
</ul>
</li>
<li><strong>或是假如</strong> {{HTTPHeader("Content-Type")}} 標頭有除了下方所列出以外的值:
<ul>
<li><code>application/x-www-form-urlencoded</code></li>
<li><code>multipart/form-data</code></li>
<li><code>text/plain</code></li>
</ul>
</li>
<li><strong>或是假如</strong>一或多個事件監聽器被註冊到一個用來發出請求的 {{domxref("XMLHttpRequestUpload")}} 物件上。</li>
<li><strong>或是假如</strong>請求中有一個 {{domxref("ReadableStream")}} 物件被於上傳。</li>
</ul>
<div class="note"><strong>備註:</strong>WebKit Nightly 與 Safari Technology Preview 對 {{HTTPHeader("Accept")}}、{{HTTPHeader("Accept-Language")}} 及 {{HTTPHeader("Content-Language")}} 標頭值加入了額外的限制。假如這三個標頭中有任一個擁有「非標準」值,WebKit/Safari 便會傳送預檢請求。WebKit/Safari 並沒有於文件中定義何者為「非標準」值,只有在以下 WebKit bugs 中討論:<a href="https://bugs.webkit.org/show_bug.cgi?id=165178" rel="nofollow noreferrer">Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language</a>、<a href="https://bugs.webkit.org/show_bug.cgi?id=165566" rel="nofollow noreferrer">Allow commas in Accept, Accept-Language, and Content-Language request headers for simple CORS</a> 和 <a href="https://bugs.webkit.org/show_bug.cgi?id=166363" rel="nofollow noreferrer">Switch to a blacklist model for restricted Accept headers in simple CORS requests</a>。其它的瀏覽器沒有實作這些額外的限制,因為這並不是規範中的一部分。</div>
<p>下面是一段會引起預檢請求的範例:</p>
<pre class="brush: js" id="line1">var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
function callOtherDomain(){
if(invocation)
{
invocation.open('POST', url, true);
invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
invocation.setRequestHeader('Content-Type', 'application/xml');
invocation.onreadystatechange = handler;
invocation.send(body);
}
}
......
</pre>
<p>在這個例子中,第 3 行建立了一段 XML 內容資料並於第 8 行使用 <code>POST</code> 請求送出。而在第 9 行,設定了一個自定義的(非標準)之 HTTP 請求標頭(<code>X-PINGOTHER: pingpong</code>)。此標頭並非 HTTP/1.1 通訊協定的一部分,但廣泛的使用於 Web 應用程式。而因為請求的 Content-type 為 <code>application/xml</code>,且設定了自定義標頭,故此請求為預檢請求。</p>
<p><img alt="" src="https://mdn.mozillademos.org/files/16753/preflight_correct.png" style="height: 553px; width: 521px;"></p>
<p>我們來看看客戶端與伺服器端之間完整的交換資訊。第一次的交換為<em>預檢請求/回應</em>:</p>
<pre class="brush: none">OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
</pre>
<p>一旦預檢請求完成,真正的請求才會被送出:</p>
<pre class="brush: none">POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache
<?xml version="1.0"?><person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
</pre>
<p>第 1 - 12 行屬於 {{HTTPMethod("OPTIONS")}} 方法的預檢請求,瀏覽器依據前面的 JavaScript 程式碼決定送出預檢請求,好讓伺服器回應是否允許後續送出實際(actual)請求。OPTIONS 是一個 HTTP/1.1 方法,這個方法用來確認來自伺服器進一步的資訊,重複執行不會造成任何影響,為一{{Glossary("safe", "安全")}}方法,不會造成資源更動。除了 OPTIONS 方法,有另外兩個送出的請求標頭(分別在第 10 及 11 行):</p>
<pre class="brush: none">Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
</pre>
<p>{{HTTPHeader("Access-Control-Request-Method")}} 標頭會告訴伺服器之後送出的實際(actual)請求會是 <code>POST</code> 方法。{{HTTPHeader("Access-Control-Request-Headers")}} 標頭則是通知伺服器實際(actual)請求會帶有一個自定義的 <code>X-PINGOTHER</code> 標頭。在這些資訊下,接著伺服器將會確定是否接受請求。</p>
<p>第 14 - 26 行屬於伺服器的回應,它說明了伺服器接受 <code>POST</code> 請求方法和 <code>X-PINGOTHER</code> 標頭。另外讓我們特別來看看 17 - 20 行:</p>
<pre class="brush: none">Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400</pre>
<p>伺服器回應中的 <code>Access-Control-Allow-Methods</code> 標頭表示伺服器可以接受 <code>POST</code>、<code>GET</code> 和 <code>OPTIONS</code> 方法。請注意此標頭和 {{HTTPHeader("Allow")}} 十分相似,但它只在存取控制範圍下才有意義。</p>
<p>伺服器也回傳了 <code>Access-Control-Allow-Headers</code> 標頭及其值「<code>X-PINGOTHER, Content-Type</code>」,表示伺服器允許在實際(actual)請求中使用以上這兩個標頭。與 <code>Access-Control-Allow-Methods</code> 相同,<code>Access-Control-Allow-Headers</code> 也是用逗號分隔可接受的標頭名稱。</p>
<p>最後,{{HTTPHeader("Access-Control-Max-Age")}} 提供了本次預檢請求回應所可以快取的秒數。在此範例中,86400 秒即為 24 小時。請留意每一個瀏覽器都有<a href="/zh-TW/docs/Web/HTTP/Headers/Access-Control-Max-Age">預設的最大值</a>,當 <code>Access-Control-Max-Age</code> 較預設值大時會優先採用預設值。</p>
<h4 id="預檢請求和重新導向">預檢請求和重新導向</h4>
<p>目前大多瀏覽器不支援預檢請求時的重新導向,如果預檢請求進行中發生重新導向,目前大多的瀏覽器會回報類似以下的錯誤訊息。</p>
<blockquote>
<p>The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight</p>
</blockquote>
<blockquote>
<p>Request requires preflight, which is disallowed to follow cross-origin redirect</p>
</blockquote>
<p>CORS 通訊協定最初要求此預檢請求重新導向的行為,但<a href="https://github.com/whatwg/fetch/commit/0d9a4db8bc02251cc9e391543bb3c1322fb882f2">在隨後的修訂中即改為不要求使用</a>。然而,大多數的瀏覽器尚未實作此變動,且仍舊依照原本的行為要求。</p>
<p>因此直到瀏覽器趕上規範之前,你可以使用下列一或兩種方法來解決這個限制:</p>
<ul>
<li>變更伺服器端的行為以避免預檢以及/或是避免重新導向——假如你對被請求的伺服擁有控制權</li>
<li>變更請求為<a href="zh-TW/docs/Web/HTTP/Access_control_CORS#Simple_requests">簡單請求</a>,讓預檢不會發生</li>
</ul>
<p>但若難以實施以上方法,仍有其他可行的方式:</p>
<ol>
<li>建立一個<a href="#Simple_requests">簡單請求</a>來測定(使用 Fetch API 的 <a href="/zh-TW/docs/Web/API/Response/url">Response.url</a> 或 <a href="https://developer.mozilla.org/zh-TW/docs/Web/API/XMLHttpRequest/responseURL">XHR.responseURL</a> 來測定預檢請求最終真正導向的 URL)。</li>
<li>建立另一個請求(「真正的」請求)傳送至第一步自 <a href="/zh-TW/docs/Web/API/Response/url">Response.url</a> 或 <a href="https://developer.mozilla.org/zh-TW/docs/Web/API/XMLHttpRequest/responseURL">XHR.responseURL</a> 所獲得的 URL。</li>
</ol>
<p>然而,假如請求是由於存在 <code>Authorization</code> 標頭而觸發預檢,便無法利用以上的步驟來解除限制。並且直到你對被請求的伺服擁有控制權前,沒有其他方式能夠解決。</p>
<h3 id="附帶身分驗證的請求">附帶身分驗證的請求</h3>
<p>{{domxref("XMLHttpRequest")}} 或 <a href="https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API">Fetch</a> 在 CORS 中最有趣的功能為傳送基於 <a href="/zh-TW/docs/Web/HTTP/Cookies">HTTP cookies</a> 和 HTTP 認證(Authentication)資訊的「身分驗證(credentialed)」請求。預設情況下,在跨站 {{domxref("XMLHttpRequest")}} 或 <a href="https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API">Fetch</a> 呼叫時,瀏覽器<strong>不會</strong>送出身分驗證。必須要於 {{domxref("XMLHttpRequest")}} 物件中或是在呼叫 {{domxref("Request")}} 建構式時設置一個特定的旗標。</p>
<p>在這個範例中,一個來自 <code class="plain">http://foo.example</code> 的內容發出了一個簡單的 GET 去請求一個 <code class="plain">http://bar.other</code> 的資源,且該站會設定 Cookies。foo.example 的內容可能包含類似的 JavaScript:</p>
<pre class="brush: js" id="line1">var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}</pre>
<p>第 7 行秀出了一個於 {{domxref("XMLHttpRequest")}} 中為了要搭配 Cookies 進行呼叫而必須設置的布林值旗標——<code>withCredentials</code>。在預設情況下,請求呼叫是不會有 Cookies 的。由於這是一個簡單 <code>GET</code> 請求,並不會進行預檢,但瀏覽器將會<strong>拒絕</strong>任何沒有 {{HTTPHeader("Access-Control-Allow-Credentials")}}<code>: true</code> 標頭值的回應,並且<strong>不讓</strong>呼叫的網站內容存取該回應。</p>
<p><img alt="" src="https://mdn.mozillademos.org/files/14291/cred-req.png" style="height: 223px; width: 521px;"></p>
<p>下面是一個簡單的客戶端與伺服器端之間的交換資訊:</p>
<pre class="brush: none">GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
[text/plain payload]
</pre>
<p>雖然第 11 行包含了預定要給予 <code class="plain">http://bar.other</code> 來取得資源內容的 Cookie,但假如 bar.other 沒有於回應中帶有 {{HTTPHeader("Access-Control-Allow-Credentials")}}<code>: true</code> 標頭值(第 19 行),則回應將會被乎略且不提供給網站內容使用。</p>
<h4 id="身分驗證請求與萬用字元">身分驗證請求與萬用字元</h4>
<p>在回應一個身分驗證請求時,伺服器<strong>必須</strong>於 <code>Access-Control-Allow-Origin</code> 標頭值中指定一個來源,而不是使用「<code>*</code>」萬用字元(wildcard)。</p>
<p>上方範例的請求標頭中包含了一個 <code>Cookie</code> 標頭,若 <code>Access-Control-Allow-Origin</code> 標頭為「*」,則請求將會失敗。範例中的 <code>Access-Control-Allow-Origin</code> 標頭值為「<code class="plain">http://foo.example</code>」(一個實際的來源)而不是「*」萬用字元,所以身分驗證證明內容被回傳予呼叫的網站內容中。</p>
<p>請注意上面範例中的 <code>Set-Cookie</code> 回應標頭也設定了另一個 cookie。萬一失敗,會拋出一個錯誤(取決於所使用的 API)。</p>
<h4 id="第三方_cookies">第三方 cookies</h4>
<p>請注意,在 CORS 回應中設定的 cookies 受制於一般的第三方 cookie 政策。在上面的範例中,頁面載入自 <code>foo.example</code>,但第 22 行的 cookie 為 <code>bar.other</code> 所傳送,因此如果使用者將其瀏覽器設定為拒絕所有第三方 cookies,則 cookies 不會被保存。</p>
<h2 id="HTTP_回應標頭">HTTP 回應標頭</h2>
<p>這個小節列出了伺服器回傳予取存控制請求之由跨來源資源共用規範所定義的 HTTP 回應標頭。上一節已提供了這些行為的概述。</p>
<h3 id="Access-Control-Allow-Origin">Access-Control-Allow-Origin</h3>
<p>一個回應的資源可能擁有一個 {{HTTPHeader("Access-Control-Allow-Origin")}} 標頭,如以下的語法:</p>
<pre class="brush: none">Access-Control-Allow-Origin: <origin> | *
</pre>
<p><code>origin</code> 參數指定了一個可以存取資源的 URI。瀏覽器必定會執行此檢查。對一個<strong>不帶有</strong>身分驗證的請求,伺服器可以指定一個「*」作為萬用字元(wildcard),從而允許任何來源存取資源。</p>
<p>舉例來說,要允許 http://mozilla.org 存取資源,你可以指定:</p>
<pre class="brush: none">Access-Control-Allow-Origin: http://mozilla.org</pre>
<p>如果伺服器指定了一個來源主機而不是「*」,那也可能於不同回應的標頭中包含不同之來源,來向客戶端表示伺服器的回應會因請求標頭之 <code>Origin</code> 值而有所不同。</p>
<h3 id="Access-Control-Expose-Headers">Access-Control-Expose-Headers</h3>
<p>{{HTTPHeader("Access-Control-Expose-Headers")}} 標頭表示伺服器允許瀏覽器存取回應標頭的白名單,如:</p>
<pre class="brush: none">Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
</pre>
<p>這允許了瀏覽器能夠存取回應當中的 <code>X-My-Custom-Header</code> 以及 <code>X-Another-Custom-Header</code> 標頭。</p>
<h3 id="Access-Control-Max-Age">Access-Control-Max-Age</h3>
<p>{{HTTPHeader("Access-Control-Max-Age")}} 標頭表示了預檢請求的結果可以被快取多長的時間,請參考上面的範例。</p>
<pre class="brush: none">Access-Control-Max-Age: <delta-seconds>
</pre>
<p><code>delta-seconds</code> 參數代表預檢請求之結果可以被快取的秒數。</p>
<h3 id="Access-Control-Allow-Credentials">Access-Control-Allow-Credentials</h3>
<p>{{HTTPHeader("Access-Control-Allow-Credentials")}} 標頭表示了當請求的 <code>credentials</code> 旗標為真時,是否要回應該請求。當用在預檢請求的回應中,那就是指示後續的實際請求可否附帶身分驗證。請注意,由於簡單的 <code>GET</code> 請求沒有預檢,所以如果一個簡單請求帶有身分驗證,同時假設此標頭沒有與資源一併回傳,則回應會被瀏覽器所忽略並且不會回傳予呼叫的網站內容。</p>
<pre class="brush: none">Access-Control-Allow-Credentials: true
</pre>
<p><a class="internal" href="#Requests_with_credentials">驗證請求</a>在上面的討論當中。</p>
<h3 id="Access-Control-Allow-Methods">Access-Control-Allow-Methods</h3>
<p>{{HTTPHeader("Access-Control-Allow-Methods")}} 標頭表示存取資源所允許的方法,用來回應預檢請求。上面已討論請求之預檢的條件。</p>
<pre class="brush: none">Access-Control-Allow-Methods: <method>[, <method>]*
</pre>
<p>一個<a class="internal" href="#Preflighted_requests">預檢請求的範例已在上面提供</a>,其中包含了一個回傳此標頭予瀏覽器的例子。</p>
<h3 id="Access-Control-Allow-Headers">Access-Control-Allow-Headers</h3>
<p>{{HTTPHeader("Access-Control-Allow-Headers")}} 標頭用在回傳予預檢請求的回應當中,以指定哪些 HTTP 標頭可以於實際請求中使用。</p>
<pre class="brush: none">Access-Control-Allow-Headers: <field-name>[, <field-name>]*
</pre>
<h2 id="HTTP_請求標頭">HTTP 請求標頭</h2>
<p>這個小節列出了當客戶端為了跨來源資源共用而傳送 HTTP 請求時可能會使用到的標頭。請注意這些標頭為對伺服器呼叫時手動設定,開發者使用跨站 {{domxref("XMLHttpRequest")}} 時則不須於程式中設定任何的跨來源資源共用請求標頭。</p>
<h3 id="Origin">Origin</h3>
<p>{{HTTPHeader("Origin")}} 標頭表示了跨站存取請求或預檢請求的來源。</p>
<pre class="brush: none">Origin: <origin>
</pre>
<p>其值為一個告訴目標伺服器之請求傳送來源的 URI。並不含有任何路徑資訊,僅有伺服器名稱。</p>
<div class="note"><strong>備註:</strong><code>origin</code> 標頭可設定為空字串;這對不是真實位置的情況來說相當有用,例如來源為一個 <code>data</code> URL 時。</div>
<p>請注意在任何存取控制請求中,{{HTTPHeader("Origin")}} 標頭<strong>永遠</strong>都要送出。</p>
<h3 id="Access-Control-Request-Method">Access-Control-Request-Method</h3>
<p>{{HTTPHeader("Access-Control-Request-Method")}} 標頭用在發出的預檢請求中,告訴伺服器後續實際(actual)請求所用的 HTTP 方法。</p>
<pre class="brush: none">Access-Control-Request-Method: <method>
</pre>
<p>此標頭的相關範例可參考<a class="internal" href="#Preflighted_requests">上方說明</a>。</p>
<h3 id="Access-Control-Request-Headers">Access-Control-Request-Headers</h3>
<p>{{HTTPHeader("Access-Control-Request-Headers")}} 標頭用在發出的預檢請求中,告訴伺服器端後續實際(actual)請求所帶的 HTTP 標頭。</p>
<pre class="brush: none">Access-Control-Request-Headers: <field-name>[, <field-name>]*
</pre>
<p>此標頭的相關範例可參考<a class="internal" href="#Preflighted_requests">上方說明</a>。</p>
<h2 id="規範">規範</h2>
<table class="standard-table">
<tbody>
<tr>
<th scope="col">Specification</th>
<th scope="col">Status</th>
<th scope="col">Comment</th>
</tr>
<tr>
<td>{{SpecName('Fetch', '#cors-protocol', 'CORS')}}</td>
<td>{{Spec2('Fetch')}}</td>
<td>New definition; supplants <a href="https://www.w3.org/TR/cors/">W3C CORS</a> specification.</td>
</tr>
</tbody>
</table>
<h2 id="瀏覽器相容性">瀏覽器相容性</h2>
<p>{{Compat("http.headers.Access-Control-Allow-Origin")}}</p>
<h3 id="相容性備註">相容性備註</h3>
<ul>
<li>IE8 和 IE9 支援 CORS 透過 XDomainRequest 物件,IE10 開始則完全正常支援。</li>
<li>Firefox 3.5 引進支援跨站 XMLHttpRequests 與 Web Fonts,較舊版本上某些請求會受到限制。Firefox 7 引進支援 WebGL 紋理的跨站 HTTP 請求,而 Firefox 9 新增支援使用 <code>drawImage</code> 方法將圖形繪製於 canvas 中。</li>
</ul>
<h2 id="參見">參見</h2>
<ul>
<li><a class="external" href="http://arunranga.com/examples/access-control/">Code Samples Showing <code>XMLHttpRequest</code> and Cross-Origin Resource Sharing</a></li>
<li><a href="https://github.com/jackblackevo/cors-jsonp-sample">Client-Side & Server-Side (Java) sample for Cross-Origin Resource Sharing (CORS)</a></li>
<li><a class="internal" href="/zh-TW/docs/Web/HTTP/Server-Side_Access_Control">Cross-Origin Resource Sharing From a Server-Side Perspective (PHP, etc.)</a></li>
<li><a class="external" href="http://www.w3.org/TR/cors/">Cross-Origin Resource Sharing specification</a></li>
<li>{{domxref("XMLHttpRequest")}}</li>
<li><a href="/zh-TW/docs/Web/API/Fetch_API">Fetch API</a></li>
<li><a class="external" href="http://www.kendoui.com/blogs/teamblog/posts/11-10-03/using_cors_with_all_modern_browsers.aspx">Using CORS with All (Modern) Browsers</a></li>
<li><a href="http://www.html5rocks.com/en/tutorials/cors/">Using CORS - HTML5 Rocks</a></li>
</ul>
|