Cross-Origin Resource Sharing 簡稱 CORS,中文為跨來源資源共享。
上一篇提到web瀏覽器有同源政策的限制,而CORS則是一種安全確認機制,讓瀏覽器和伺服器之間能確保安全的進行cross origin資源共享,即若伺服器同意,即可達成跨來源資源共享。
CORS 把 Request 分成兩種,一種是簡單請求,另一種是非簡單請求。
簡單請求(以下條件皆滿足):
1. HTTP method為: 「GET」 或 「HEAD」 或 「POST」。
2. Content-type標頭值為:「application/x-www-form-urlencoded」 或 「multipart/form-data」 或 「text/plain」。
3. 僅手動設定定義為「CORS 安全列表請求標頭(CORS-safelisted request-header)」的標頭,如Accept, Accept-Language, Content-Language, Content-Type...等等。
4. 沒有事件監聽器被註冊到任何用來發出請求的 XMLHttpRequestUpload 物件(經由 XMLHttpRequest.upload 屬性取得)上。
5. 請求中沒有 ReadableStream 物件被用於上傳。
- 以下直接用程式碼實作來了解CORS簡單請求:
前端index.html執行結果如下:
可以看到console出現了: Access to XMLHttpRequest at 'http://192.168.56.1/cors/index.php' from origin 'http://127.0.0.1' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
瀏覽器在收到Server response 之後,會先檢查Server response header中的Access-Control-Allow-Origin裡面是否有包含發起 Request 的 Origin,有的話就會允許通過,讓js順利接收到 Response。
由於這個例子index.php並沒有設定Access-Control-Allow-Origin response header,因此這個跨源存取被擋住了!
因此,如果我的網站javascript想拿到別人網站server回傳的資料,關鍵在於,那個網站的server response header中的Access-Control-Allow-Origin是否有包含我的網站的origin。
這邊修改一下index.php:
改成允許從127.0.0.1來的origin。
response header中的Access-Control-Allow-Origin已經包含了http://127.0.0.1,因此成功拿到資料了!
非簡單請求(以下任一點條件滿足):
1. HTTP method為: PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH 任一個。
2. Content-type標頭值為除了這些以外的值:「application/x-www-form-urlencoded」 或 「multipart/form-data」 或 「text/plain」。
3. 請求中包含了任何除了「CORS 安全列表請求標頭(CORS-safelisted request-header)」的標頭,如Accept, Accept-Language, Content-Language, Content-Type...等等。
4. 一或多個事件監聽器被註冊到一個用來發出請求的 XMLHttpRequestUpload 物件上。
5. 請求中有一個 ReadableStream 物件被於上傳。
非簡單請求會先發送一個預檢請求(preflight),若server同意後,瀏覽器才會真正發出要資料的request。
- 以下直接用程式碼實作來了解:
這次送出的request contentType改成application/json,因此變成非簡單請求了!
執行結果如下:
console如上圖,Access to XMLHttpRequest at 'http://192.168.56.1/cors/index.php' from origin 'http://127.0.0.1' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
首先發出一個預檢請求,OPTIONS method的request,response header如上圖,還沒設定Access-Control-Allow-Origin。
request header如上圖。
接著修改index.php,加入Access-Control-Allow-Origin:
console如下:
Access to XMLHttpRequest at 'http://192.168.56.1/cors/index.php' from origin 'http://127.0.0.1' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
response/request header變為這樣:
從console的提示可以得知,預檢請求(preflight)的response header中沒有設定Access-Control-Allow-Headers允許content-type。
因此再將index.php改成這樣:
console如下:
preflight response/request header如下:
預檢請求中送出的header: Access-Control-Request-Method 是通知server之後送出的實際請求會是 POST 方法。
Access-Control-Request-Headers 則是通知server之後送出的實際請求會帶有一個 content-type 標頭。
因此server必須在response header中設定允許:
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST
這邊之所以沒設定Access-Control-Allow-Methods: POST是因為預設是允許的。
當瀏覽器檢查到預檢請求的server response header中有設定上述的allow項目後,之後就可以看到後面成功送出實際請求了:
由此可知,非簡單請求會預先送出預檢請求,並且確保server有允許實際請求的method, headers, origin 等等,後續才會送出真正的實際請求。