那些經歷過的 CORS 蠢問題

Yovan
8 min readJan 18, 2019

--

CORS 技術被使用很多年了,以往Server只要再回傳的HttpRepsonse header加上一些特定Tag就能夠簡單的使用,一段時間後又忘了,只好好好記錄一下。

最近 release 給前端測試的 API 都是需要認證,最方便和簡單的方式就是請前端在 request header 加上 Authorization,這時候我天真的以為跟以前一樣加上簡易的filter就能搞定!?

(client) add Authorization to request header
(server) first simple version to append specific symbols to response header

我發現我錯了,只是多個 Authorization,就是不給我過呢,而且怎麼會發兩次的 Request,Request Method 還是 Options... 只好研究一下 CORS 機制 !

什麼是 CORS ?!

CORS 全名叫 Cross-Origin Resource Sharing ,一種用來解決同源政策(Same-origin policy)

Same-origin policy — Browser 禁止從 A 網站 網頁使用 AJAX (XMLHttpRequest or Fetch) 呼叫 B 網站的 API

過去還有另一種克服方式 JSONP ,目前以 CORS 為主流, JSONP 方法我就沒應用過了。

#CORS 原理

藉由添加新的訊息在 HTTP Header,讓 Server 能夠描述可提供的資源訊息, 因此Browser 有資訊判斷 Server 是否滿足 Request 的條件。所以 CORS 機制需要 Browser 和 Server 同時支持才行!除了相關設定之外,整個程序 Browser 都會處理好。

// Server 描述的資源訊息 e.g.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: false
Access-Control-Expose-Headers: X-My-Custom-Header
Access-Control-Max-Age: 86400

Access-Control-Allow-Methods

  • 表明 Server 所支持的所有跨網域請求的方法,每個值用逗號當分隔

Access-Control-Allow-Headers

  • 表明 Server 所支持的 Header 訊息

Access-Control-Allow-Credentials

  • 表明 Server 是否允許發送Cookie,預設是 False。如果要發送Cookie,Access-Control-Allow-Origin 就不能設為 * ,就必須要明確指定
假設 Server 設定為允許,Client withCredentials 屬性也需要設為 True 才會連 Cookie 一並發送。#Server 
Access-Control-Allow-Credentials: true
#Client
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

Access-Control-Max-Age

* 指定本次 Preflight Request 的有效期限,以秒為單位

#CORS流程

發起一個 CORS Request ,Browser 會在 Request Header 添加一個 Origin 資訊,代表這 Request 發起的來源 (protocol + domain + port)。

GET /corstest/hi HTTP/1.1
Origin: http://yovan.example
Host: api.example
Connection: keep-alive
User-Agent: Mozilla/5.0...

這時候 Browser 處理上,將 CORS Request 分為兩類,Simple Request Not-so-simple Request。

1.Request Method:
# GET
# POST
# HEAD
2.HTTP Header Information:
# Accept
# Accept-Language
# Content-Language
# Last-Event-ID
# Content-Type:
- text/plain
- application/x-www-form-urlencoded
- multipart/form-data
只要同時滿足上述兩大條件,就屬於 Simple Request,其餘都屬於 Not-so-simple Request。

Simple Request

直接發送實際的 CORS Request,然後 Browser 會依據 Response Header 的 Access_Control-Allow-Origin 決定是否同意這次 Request,如果沒有這資訊,此次的 Request 也是不被允許。

GET /corstest/hi HTTP/1.1
Origin: http://yovan.example
Host: api.example
Connection: keep-alive
User-Agent: Mozilla/5.0...
HTTP/1.1 200 OK
Date: Fri, 18 Jan 2019 02:49:53 GMT
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: *

Not-so-simple Request

跟 Simple Request 不同的是,Browser 會先送一個 Preflight Request (預檢請求) — Request Method : Options

確認 Server 可支持的項目後,都符合條件的話, Browser 才會送出實際的 CORS Request。

只要通過了 Preflight Request
Access-Control-Max-Age 設定時間內,同樣的 CORS Request 簡化成 Simple Request 模式,Browser 只需要送出實際的 CORS Request

原來 Browser 幫我們處理了大部分 CORS 事情,而 Server 只要提供需要支持的相關設定加入到 Response Header 就能運作,最後都是由 Browser 決定CORS Request 是否被允許。 並且在了解 Preflight Request 的機制後,發現我一開始遇到的問題 (Preflight Request 一直收到 HTTP Status 302) , 如果說 Preflight Request 只是要知道 Server 可以允許哪些項目,我們是不是對於Preflight Request 部分,在寫入相關Header後直接回傳,也就是不需要再執行 Filter Chain 的其他 Filter。

針對 preflight request (Method:Options),不需要在執行其他 filter

終於成功拉 !

successfully fetched cors data

後記

後來翻了一下 Spring Security ,4.2版本以後也已經有實作 CorsFilter ,對照一下也是 Preflight Request 也會直接 return

Refers

https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS

http://www.ruanyifeng.com/blog/2016/04/cors.html

--

--

Yovan

時間不會為誰而停留,就算暫停了全世界的鐘,也停不了這一秒鐘,所以有什麼理由,不好好把握每一刻! 希望現在開始透過紀錄,留下在軟體開發的路途上,每個走過的足跡! www.linkedin.com/in/yovan-li