摘要
談到跨域,不論前端還是後端,多少有點談虎色變,面試中也常會問到這些問題,瀏覽器和伺服器端到底怎麼做才能跨域,他們都做了什麼?
同源 vs 跨域
同源,字面意義是相同的源頭,即同一個web伺服器(比如tomcat啟動的一個例項),好比一個家庭;跨域就是從一個web伺服器向另一個伺服器傳送或獲取資料,好比去鄰居家拿東西。之所以要做跨域限制,是為了安全,你不能從鄰居家直接拿吧,總得問過人家。
那麼怎麼來判斷同源?就看一個web伺服器的根本三要素:協議、web伺服器域名、埠號。
比如:當前頁面的地址是http://myhost.com:8080/index
,源就是http://myhost.com:8080
,當新請求的協議、域名、埠號與之完全一致即是同源,否則是跨域。
對於下面傳送的請求,瀏覽器的判決如下:
http://
www.myhost.com:8080/users
跨域 ->
域名必須完全一致- https
://myhost.com:8080/users
跨域 ->
協議必須一致 https://myhost.com:
9000/users
跨域 ->
埠必須一致https://myhost.com/users
跨域 ->
埠必須一致(沒有埠也是不一致)https://myhost.com:8080/profile/users
同源
跨域時的動作
瀏覽器端
假設我們點選一個按鈕去獲取資料,獲取資料的請求準備從瀏覽器發出,這時瀏覽器先會檢測這條請求是同源還是跨域,也就是與按鈕所在頁面的地址是同源還是跨域,如果是同源,好說,直接傳送出去;如果是跨域的請求,那就得hold住先,瀏覽器會在請求的http header中加上一個Origin欄位,標明這個請求是從哪裡發出來的,例如: Origin:http://neighbour.com:9000
,這樣伺服器端好辨識是自己家人來取東西,還是隔壁老王來借東西了。
那些做檢測、加header欄位等事情全是瀏覽器做,對於前端開發者來說,什麼事都不用幹,ajax請求平時怎麼傳送,跨域時還怎麼傳送。可見,跨域對於前端沒影響,關鍵在於伺服器端。
伺服器端
每個web伺服器就像一個家庭。好鄰居要吃螃蟹來借點醋,這沒問題;壞鄰居家有醋要來借點螃蟹,這不幹。
伺服器收到請求會給與響應,響應的header裡寫明跨域的配置資訊,告訴瀏覽器,它允許哪些域名發來的請求訪問,哪些method可以執行。瀏覽器收到響應後自動判斷能不能真正執行請求。
假設伺服器域名是http://rich.com:8080
,它收到了一個從http://neighbour.com:9000
發來的請求http://rich.com:9000/borrow-vinegar
,伺服器響應它,在響應中寫明本伺服器支援哪些域名可以訪問,哪些method可以執行,瀏覽器收到後做匹配,再判定。
那麼,伺服器有哪些判定的規則呢?
是否允許跨域的判定
一個支援CORS的web伺服器,有如下的判定欄位,他們會在響應的header中寫明
- Access-Control-Allow-Origin:允許跨域的Origin列表
- Access-Control-Allow-Methods:允許跨域的方法列表
- Access-Control-Allow-Headers:允許跨域的Header列表
- Access-Control-Expose-Headers:允許暴露給JavaScript程式碼的Header列表
- Access-Control-Max-Age:最大的瀏覽器快取時間,單位為s
其中Access-Control-Allow-Origin
(訪問控制之允許的源),在響應的http header中必須有的,表示允許訪問本伺服器的源頭Origin
(域名),可以是特定的域名列表,用逗號分隔,也可以是萬用字元 *
,表示支援任意域名的訪問。
除了限定源頭Origin
,還會限制請求的方法Method
,Header
。
如,如果伺服器設定Access-Control-Allow-Methods:GET
,那麼跨域的POST請求無法在這個伺服器執行。
總流程
- 頁面傳送請求
- 瀏覽器根據同源策略做出判定,如果是同源請求,直接傳送出去;如果是跨域請求,在HTTP HEADER加上Origin欄位,或是先傳送一次預檢請求(preflight)。
- 伺服器接收請求,根據自身跨域的配置(如允許哪些域名,什麼樣的Method訪問),返回檔案頭。若未配置過任何允許跨域,則檔案頭裡不包含
Access-Control-Allow-origin
欄位,若配置過域名,則返回Access-Control-Allow-origin+ 對應配置規則裡的域名
的方式。 - 瀏覽器接收到響應,根據響應頭裡的`Access-Control-Allow-origin欄位做匹配,如果沒有這個欄位,說明不匹配;如果有,將欄位內容和當前域名做比對。如匹配,則可以傳送請求。
跨域的請求形式
跨域的請求分兩種,一種是簡單請求,一種是非簡單請求(廢話)。
- 簡單請求,方法僅限於
HEAD,GET或POST
,且Header的欄位不超過以下欄位: - HeadeAccept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain (沒有application/json, 說明如果傳送JSON格式的body請求資料是一個非簡單請求)
- 非簡單請求就是其他請求
簡單請求瀏覽器會直接在請求的Header加上Origin欄位再傳送;非簡單請求瀏覽器則會先傳送一次預檢請求,根據預檢請求的結果,決定是否正式傳送請求。
詳情可以訪問阮一峰的日誌:[跨域資源共享 CORS 詳解][1][1]: www.ruanyifeng.com/blog/2016/0…