PJCHENder 未整理筆記

同源政策與跨來源資源共用(CORS)

2018-08-20

[JS] 同源政策與跨來源資源共用(CORS)

@(JavaScript)[javascript]

keywords: CORS, Cross-Origin Resource Sharing, 同源策略, Same-origin policy

同源政策(same-origin policy)

在同源政策(same-origin policy)中規範了那寫資源可以跨源存取,哪些會受到限制。

同源的定義簡單如下:

  • 不同網域(Domain)
  • 不同通訊協定:HTTP, HTTPS, FTP
  • 不同連接埠號(Port)

一般來說跨來源寫(Cross-origin writes)、跨來源嵌入(Cross-origin embedding)是被允許的,而跨來源讀取(Cross-origin reads)是受限制的。

同源政策 @ MDN

跨來源資源共用(CORS)

1
2
3
4
5
6
fetch('https://www.google.com')
.then(function(response) {
// Do something ...
}).catch(function(error) {
// Do something else ...
})

這時候會出現經典的 Cross-Origin Resource Sharing(CORS) 問題:

Imgur

這個問題會在當你向要資料的網站處在不同源的情況下會發生,這是基於瀏覽器安全性考量所定義的規範,稱做同源政策(Same Origin Policy),也就是說程式碼所發出的跨來源 HTTP 請求(cross-origin HTTP Request)會受到限制,。

基於安全性考量,程式碼所發出的跨來源 HTTP 請求會受到限制。例如,XMLHttpRequestFetch 都遵守同源政策(same-origin policy)。這代表網路應用程式所使用的 API 除非使用 CORS 標頭,否則只能請求與應用程式相同網域的 HTTP 資源。

在這個情況下,其實請求(request)已經發出去了,而瀏覽器其實也拿到回應(response),但是瀏覽器基於同源政策,因此不把拿到的回應給你的 JavaScript 去做進一步的處理。

開啟跨來源請求

若要開啟跨來源請求,必須在伺服器端做一些設定,像是在 Response Header 加上 Access-Control-Allow-Origin

1
2
Access-Control-Allow-Origin: *            # 允許所有網站發送的請求
Access-Control-Allow-Origin: http://foo.example # 只允許 http://foo.example 的請求

這裡 * 就表示接受所有不同來源的跨域請求,因此當瀏覽器接受到伺服器的回應時,會去比對這個內容,如果目前的 Origin 符合 Access-Control-Allow-Origin 所定義的規則的話,瀏覽器才會把回應給你。

另外還有其他和跨域請求時可用的 Header,像是:

1
2
3
4
5
Access-Control-Allow-Headers:
Access-Control-Allow-Methods:
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
Access-Control-Max-Age: <delta-seconds>
Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Headers:指定哪些 HTTP 標頭可以於實際請求中使用。
  • Access-Control-Allow-Methods:存取資源所允許的方法,用來回應預檢請求。
  • Access-Control-Expose-Headers:瀏覽器能夠存取伺服器回應當中哪些標頭。
  • Access-Control-Max-Age:預檢請求之結果可以被快取的秒數。
  • Access-Control-Allow-Credentials:用於驗證請求中。

簡單請求與預檢請求

請求又會分成「簡單請求(Simple Request)」和「預檢請求(preflight request)」。

簡單請求(Simple Request)

簡單請求有一些規範,基本上使用的一定要是 GET、HEAD、POST 方法,並且僅允許特定的標頭和內容,如此才不算是簡單情求。

簡單請求(Simple Request) @ MDN > CORS

備註:雖然這些都是網頁目前已經可以送出的跨站請求,但除非伺服器回傳適當標頭,否則不會有資料回傳,因此不允許跨站請求的網站無須擔心會受到新的 HTTP 存取控制影響。

預檢請求(Preflight Request)

預檢請求通常是在發送會帶有副作用的 HTTP 請求方法前,規範瀏覽器要先發送預檢請求,預檢請求會以 HTTP OPTIONS 的方法送出,以向伺服器確認後續的請求能否傳送,如果預檢請求沒有通過,那麼後續真正要發送的實際請求(例如,POST, PUT, DELETE 等等)就不會發送了。

預檢請求和簡單請求有一點不同,如果是簡單請求而被 CORS 攔下來的話,實際上請求已經發送出去,只是被瀏覽器檔下來不給你;但若是預檢請求,發送預檢請求後若沒有通過,真正要發送的請求是不會發送出去的,也就是說「一旦預檢請求完成,真正的請求才會被送出」。

預檢請求流程

imgur

用戶端向伺服器端發送預檢請求
1
2
3
4
5
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
伺服器回應預檢請求

在這些資訊下,接著伺服器將會確定是否接受請求,並傳送以下回覆:

1
2
3
4
5
6
HTTP/1.1 200 OK
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

伺服器回應中的:

  • Access-Control-Allow-Methods 標頭表示伺服器可以接受 POSTGETOPTIONS 方法。
  • Access-Control-Allow-Headers 標頭及其值「X-PINGOTHER, Content-Type」,表示伺服器允許在實際(actual)請求中使用以上這兩個標頭。
  • Access-Control-Max-Age 提供了本次預檢請求回應所可以快取的秒數。在此範例中,86400 秒即為 24 小時。請留意每一個瀏覽器都有預設的最大值,當 Access-Control-Max-Age 較預設值大時會優先採用預設值。

預檢請求(Preflight Request) @ MDN > CORS

參考

掃描二維條碼,分享此文章