跨域資源共享——CORS

跨域資源共享(Cross-Origin Resource Sharing)是一種機制,它使用額外的 HTTP 頭部告訴瀏覽器可讓一個web應用進行跨域資源請求。前端

請求類型

簡單請求

若一個請求同時知足下述全部條件,則該請求可視爲「簡單請求」<span style="color:grey">(注:灰色字體內容瞭解便可)</span>:ios

  • 使用的方法爲git

    • GET
    • HEAD
    • POST
  • 手動設置的頭部字段只能是(注意:也能夠設置 Forbidden header name 中的頭部字段,如 ConnectionAccept-Encoding等,可是設置無效)github

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(值的範圍還要符合下面的要求)
    • <div style="color:grey">DPR</div>
    • <div style="color:grey">Downlink</div>
    • <div style="color:grey">Save-Data</div>
    • <div style="color:grey">Viewpoer-Width</div>
    • <div style="color:grey">Width</div>
  • Content-Type 的值只能爲web

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • <div style="color:grey">No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.</div>
  • <div style="color:grey"> No ReadableStream object is used in the request.</div>

預檢請求

CORS 預檢請求發生在實際請求以前,用於檢查服務器是否支持 CORS,以判斷實際請求發送是否安全。預檢請求使用的方式是 OPTIONSaxios

當一個請求不是「簡單請求」時,即應該先發送預檢請求,好比:後端

  • 這個請求的請求方式不是GETHEADPOST
  • 或者,這個請求設置了自定義的頭部字段,好比 X-xxx
  • 或者這個請求的 Content-Type 值不是 application/x-www-form-urlencodedmultipart/form-datatext/plain,等等

跨域請求過程

跨域請求,CORS要求服務端設置一些頭部字段,最重要的一個就是 Access-Control-Allow-Origin。下面以案例進行說明,前端使用 axios 進行 http 傳輸,後端以 koa 做爲服務端框架,並使用CORS中間件 koa2-corsapi

簡單跨域請求

// Client http://localhost:8080
simpleRequest() {
  axios({
    method: 'GET',
    url: 'http://localhost:3000/api/simple'
  }).then(data => {
    console.log(data);
  });
}
// Server http://localhost:3000
app.use(cors());
router.get('/api/simple', ctx => {
  ctx.body = { result: 'simple request success' };
});

HTTP 報文:跨域

HTTP 請求頭部有個 Origin 字段,表示請求來自哪裏。HTTP 響應頭部中的 Access-Control-Allow-Origin 表示哪一個域能夠訪問該資源。使用 OriginAccess-Control-Allow-Origin 就完成了最簡單的訪問控制。瀏覽器

預檢請求&正式請求

// Client http://localhost:8080
mainRequest() {
  axios({
    method: 'POST',
    url: 'http://localhost:3000/api/mainRequest',
    headers: { 'X-test': 'CORS' } // 增長一個自定義的頭部字段,觸發預檢請求
  }).then(data => {
    console.log(data);
  });
}
// Server http://localhost:3000
app.use(cors());
router.post('/api/mainRequest', ctx => {
  ctx.body = { result: 'main request success' };
});

預檢請求的報文:

請求首部字段 Access-Control-Request-Method 告知服務器,實際請求將使用 POST 方法。
請求首部字段 Access-Control-Request-Headers 告知服務器,實際請求將攜帶一個自定義請求首部字段:x-test。服務器據此決定,該實際請求是否被容許。

響應首部字段 Access-Control-Allow-Methods 代表服務器容許客戶端使用哪些方法發起請求。
響應首部字段 Access-Control-Allow-Headers 代表服務器容許請求中攜帶字段 x-test。

實際請求的報文:

實際請求中發送了 X-test 頭部字段,響應狀態碼 200 OK。

能夠看到,預檢請求中 Client 和 Server 使用了更多的頭部字段來完成訪問控制。那麼,CORS 相關的請求頭部字段和響應頭部字段共有哪些呢?

頭部字段

HTTP 請求頭部字段

  • Origin
    Origin 頭部字段表示預檢請求或實際請求的源站。
  • Access-Control-Request-Method
    Access-Control-Request-Method 頭部字段用於預檢請求。其做用是,將實際請求所使用的 HTTP 方法告訴服務器。
  • Access-Control-Request-Headers
    Access-Control-Request-Headers 頭部字段用於預檢請求。其做用是,將實際請求所攜帶的首部字段告訴服務器。

注意,以上請求頭部字段無須手動設置,當使用 XMLHttpRequest 對象發起跨域請求時,它們已經被設置就緒。

HTTP 響應頭部字段

  • Access-Control-Allow-Origin
    其語法以下:

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

    origin 參數的值指定了容許訪問該資源的外域 URI。若是該字段的值爲通配符 *,則表示容許來自全部域的請求。
    注意,若是服務端指定了具體的域名而非 *,那麼響應頭部中的 Vary 字段的值必須包含 Origin。這將告訴客戶端:服務器對不一樣的源站返回不一樣的內容。

  • Access-Control-Allow-Methods
    Access-Control-Allow-Methods 頭部字段用於預檢請求的響應。其指明瞭實際請求所容許使用的 HTTP 方法。
  • Access-Control-Allow-Headers
    Access-Control-Allow-Headers 頭部字段用於預檢請求的響應。其指明瞭實際請求中容許攜帶的首部字段。
  • Access-Control-Expose-Headers
    跨域請求中,瀏覽器默認狀況下經過API只能獲取到如下響應頭部字段:

    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma

若是想要訪問其餘響應頭部信息,則須要在服務器端設置 Access-Control-Allow-HeadersAccess-Control-Expose-Headers 讓服務器把容許瀏覽器訪問的頭部字段放入白名單,好比:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

這樣瀏覽器就可以訪問到 X-My-Custom-HeaderX-Another-Custom-Header 響應頭部了。

  • Access-Control-Max-Age
    Access-Control-Max-Age 字段指定了預檢請求的結果可以被緩存多久,單位是 ,好比:

    Access-Control-Max-Age: 5

    表示在第一次預檢請求發出後,5s 內再訪問該接口時會直接發送實際請求,而不須要先發預檢請求。過了 5s 後,會再要求先發送預檢請求,以此類推。

    app.use(
      cors({
        maxAge: 5
      })
    );

    服務端設置了 5s 緩存,實際請求以下:

    注意,若是設置緩存後,發現每次仍是會發送 OPTIONS 請求,請檢查你是否是勾選了「禁止緩存」。

  • Access-Control-Allow-Credentials
    XMLHttpRequest.withCredentials (或者 Request.credentials)表示跨域請求中,user agent 是否應該發送 cookies、authorization headers 或者 TLS client certificates 等憑據。
    Access-Control-Allow-Credentials 的做用就是:當 credentials 爲 「真」 時(XHR和Fetch設置方式不同),Access-Control-Allow-Credentials 告訴瀏覽器是否把響應內容暴露給前端 JS 代碼。好比:

    // Client http://localhost:8080
    simpleRequest() {
      axios({
        method: 'GET',
        url: 'http://localhost:3000/api/simple',
        withCredentials: true // 增長了withCredentials 選項
      }).then(data => {
        console.log(data);
      });
    }  
    
    // Server http://localhost:3000
    app.use(
      cors({
        maxAge: 5,
        // credentials: true
      })
    );

    此時,服務端未設置 credentials: true,發起請求能看到客戶端報錯:

    若是服務端設置了 credentials: true 則客戶端就不會報錯了。

    預檢請求的時候,Access-Control-Allow-Credentials 響應頭部字段表示實際請求中是否能夠使用 credentials。

關於 CORS 響應頭部字段的運用,建議看一下 koa2-cors 中間件的源碼。代碼只有幾十行,特別清晰易懂。


CORS 相關內容如上,瞭解以後能更好地幫助咱們解決平常聯調中出現的問題,好比:出現跨域了服務端怎麼設置,axios.post 方法發送一個對象時爲何會出現 OPTIONS 請求,代理服務器怎麼才能轉發cookies等等。

相關文章
相關標籤/搜索