淺談前後端分離中的跨資源共享(CORS)

helios發表於2018-12-25

原文地址


簡介

當下無論大廠小廠的前後端開發模式都是前後端分離。以前遇到通過jsonp解決跨域的方式也漸漸的淡出的工程中(不瞭解jsonp的可以看JSONP跨域請求+簡答實現百度搜尋)。當前端請求一個介面的時候就會引起跨域,但是當下的前端構建工具都有相應的解決方案,比如webpackweb-dev-server這個外掛,就能很簡單的啟一個本地的服務,然後發請求的時候通過啟的本地服務去傳送請求,這樣就解決跨域問題的一部分了。這種情況下客戶端程式碼和正常非跨域請求一摸一樣,不用做任何改變。

上面說的方法只是解決了一部分,還有一部分我想大家可能或多或少的會遇到過,就是在登入場景的時候。服務端的response裡面有set-cookie這個欄位,在客戶端中設定cookie,cookie裡面可能包含著的seesionID表示的當前登入的使用者/當前的登入狀態(對這方面不理解的可以看通過cookie和session讓http協議變得有狀態)。當使用者已經登入並且訪問其他頁面的時候,服務端會通過cookie中的資訊去校驗使用者登入狀態,如果請求中沒有攜帶身份資訊或者身份資訊過期(服務端返回401/403)就會跳轉到登入介面。這種情況如果在前後端聯調的時候比較麻煩,因為上面方法解決的跨域是不會攜帶cookie的。目前有兩種方法去解決這個:

  1. 在登入之後拿到session/token每個請求都預設加上這個值(寫死在代理中)
  2. 在請求中增加withCredentials,服務端要設定對應的幾個響應頭,但是對服務端改動比較多。

綜上: CORS的主要任務都落在服務端,但是如果為了聯調服務端的開發程式碼和生產程式碼有區別,他們肯定是會不搞的。

背景

今天組裡的實習生在使用Axios去驗證登入的時候遇到了跨域的問題(前端是vue, 後端是Spring boot)。 一般的請求通過前端設定代理,服務端設定Access-Control-Allow-origin:\*就可以了,但是登入時候響應頭裡面要去set cookie就遇到了問題,結果由於對CORS跨域理解的不是很深刻,對預檢請求不是很瞭解,就在axios的issues裡面去搜,通過Axios doesn't send cookies with POST and data定位到了問題,原來他後端寫的攔截器裡面自動把OPTIONS這個請求給過濾掉了,沒有讓走到後面的流程。

什麼是CORS

CORS的出現是為了解決由於瀏覽器的同源策略帶來的請求跨域問題。

“跨資源共享(Cross-Origin Resource Sharing(CORS))是通過HTTP Response header來告訴瀏覽器,讓執行在一個origin(domain)上的web應用被允許訪問來自不同源伺服器上指定的資源的一種機制。”

簡單來說: CORS就是通過設定請求的響應頭(能通過開發人員控制的基本都是服務端的響應頭,客戶端的也會有對應的請求頭,但一般不會是開發人員去控制的,後面會仔細說)去控制是否允許某個origin的某個/些請求跨域。

CORS的功能

CORS標準新增了一組HTTP首部欄位,允許服務端宣告哪些源站通過瀏覽器有權訪問哪些資源。對於能對伺服器產生副作用的HTTP非簡單請求(non-simple request)(特別是除了GET請求以外的請求),瀏覽器必須首先傳送一個方法為OPTIONS的一個預檢請求(preflight request)來獲取伺服器是否允許該請求跨域。伺服器得到確認之後,才發起真正的HTTP請求。在預檢請求中,服務端也可以通知客戶端是否要攜帶Credentials.

CORS的三種請求

簡單請求

某些請求是不會觸發預檢查請求的,這些請求被成為簡單請求(simple request)。 如果一個請求滿足下列的所有條件就可以被稱為簡單請求:

  1. 使用下列的方法之一:
  • GET
  • HEAD
  • POST
  1. 除了瀏覽器自動設定的頭,只能設定Fetch 規範允許設定的“CORS安全請求頭
  1. Content-Type的值只限於下面幾個(注意沒有application/json,現在post的請求經常使用這個,所以當發post請求的時候會觸發預檢請求不要意外):
  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain
  1. 請求中的任意XMLHttpRequestUpload物件均沒有註冊任何時間監聽器。XMLHttpRequestUpload物件可以使用*XMLHttpRequest.upload *屬性訪問。
  2. 請求中沒有使用ReadableStream物件

預檢請求

不滿足上面定義的簡單請求,都會傳送預檢請求。

比如瀏覽器要傳送一個POST請求,content-Typeapplication/json,新增一個request header為X-TEST

預檢請求的步驟:

  1. 傳送一個method為OPTIONS的請求,這個請求的目的是
  • 向伺服器請求是否支援實際請求傳送的方法(是否支援post方法)
  • 向伺服器請求是否支援實際請求新增的header或者不滿足簡單請求的header
  1. 如果伺服器接受並正確返回就傳送實際的post,並且能帶上相應的header

攜帶credentials的請求

對於跨域(發生CORS)的請求預設是不會帶上憑證資訊(credentials)的,如果要傳送憑證資訊(credentials)就需要設定對應的標識位。

請求:

  • 請求中要設定withCredentials為true。

響應:

  • Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin的值不再是萬用字元*,應該是單一的origin。

HTTP規範規定Access-Control-Allow-Origin不能是萬用字元*並且只能是單一的origin。這是因為如果能設定多個的話,證明該伺服器就能接受多個域名下面的cookie,這是很危險的。

CORS中的請求頭和響應頭

響應頭

Access-Control-Allow-Origin

Access-Control-Allow-Origin: <origin> | *

origin引數的值制定了允許訪問伺服器資源的外域URI。對於不需要攜帶身份憑證的請求,伺服器可以指定這個欄位的值為萬用字元*,表示允許來自所有域的請求。

Access-Control-Expose-Headers

該頭資訊伺服器把允許瀏覽器訪問的頭放入白名單,例如:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header 在跨域訪問的時候,XHR物件的getResponseHeader()只能拿到一些最基本的響應頭。

Access-Control-Max-Age

指定了預檢請求(preflight)請求的結果能被快取多久(秒為單位)。

Access-Control-Allow-Credentials

當瀏覽器的credentials設定為true時,是否允許瀏覽器讀取response的內容

Access-Control-Allow-Methods

作為預檢請求的響應頭,指明瞭實際請求所允許的HTTP方法。

Access-Control-Allow-Headers

用於預檢請求的響應。其指明瞭實際請求中允許攜帶的首部欄位。 以逗號分割。

請求頭

這些欄位一般無需手動設定。

Origin

預檢請求或實際請求的源站。

不包含任何路徑,只是伺服器的名稱。(不管是否為跨域,這個欄位都被髮送。)

Access-Control-Request-Method

用於預檢請求。其作用是,將實際請求所使用的 HTTP 方法告訴伺服器。

參考

相關文章