如何透過設定 CORS 允許跨網站請求

通常在瀏覽器中運行的 JavaScript 應用程式只能存取由同一個網域(原始網站)提供的 HTTP 資源。

從相同來源載入圖片、腳本/樣式表一直是可行的。使用 @font-face 載入網頁字型時也已經有預設的「同源政策」。而其他較不常見的事物(例如 WebGL 紋理和 Canvas API 中載入的 drawImage 資源)也是如此。

然而,對於從外部第三方伺服器發送的 XHR 和 Fetch 請求,預設會失敗。這就是除非第三方伺服器實現了一個允許建立連線以及請求資源並下載和使用的機制。

這個機制被稱為 CORS 跨來源資源共用

需要 CORS 的一件非常重要的事情是 ES 模組,最近在現代瀏覽器中引入了。

如果您沒有在伺服器端設置允許提供第三方來源的 CORS 政策,請求將會失敗。

Fetch 範例:

由於 CORS 政策,Fetch 請求失敗

XHR 範例:

由於 CORS 政策,XHR 請求失敗

跨來源資源失敗的條件如下:

  • 不同網域
  • 不同子網域
  • 不同埠號
  • 不同協定

CORS 的目的是為了保護您的安全,防止惡意使用者利用您正在使用的 Web 平台。

如果您同時控制了伺服器和客戶端,您會知道雙方都是可信任的,因此有充分的理由允許資源共用。

如何做到這點呢?

答案取決於您使用的伺服器端堆棧。

瀏覽器支援

相當不錯(基本上所有,除了 IE<10):

CORS 瀏覽器支援

Express 範例

如果您正在使用 Node.js 和 Express 作為框架,請使用 CORS 中介軟體套件

這裡有一個簡單的 Express Node.js 伺服器範例:

const express = require('express')
const app = express()

app.get('/without-cors', (req, res, next) => {
 res.json({ msg: '😞 沒有 CORS,沒有派對!' })
})

const server = app.listen(3000, () => {
 console.log('正在監聽 port %s', server.address().port)
})

如果您從不同來源的位置對 /without-cors 發送一個 fetch 請求,它將引發 CORS 問題。

要使事情順利運作,您只需在上面的程式碼中引入上面連結的 cors 套件並將其傳遞為中介軟體函數以處理端點請求處理程式:

const express = require('express')
const cors = require('cors')
const app = express()

app.get('/with-cors', cors(), (req, res, next) => {
 res.json({ msg: '喔喔!有了 CORS 它可以正常運作了!🔝 🎉' })
})

/* 程式剩下的部分 */

我在 Glitch 上提供了一個簡單的範例,這是它的程式碼: https://glitch.com/edit/#!/flavio-cors-client

這是 Node.js Express 伺服器: https://glitch.com/edit/#!/flaviocopes-cors-example-express

請注意,由於伺服器未正確處理 CORS 標頭,儘管失敗的請求依然被接收。正如您可以在網路面板中看到的,伺服器發送了一條訊息:

CORS 無回應

僅允許特定來源

然而,此範例存在一個問題: 任何請求都會被伺服器接受為跨源請求。

如您所見,在網路面板中,通過的請求有一個回應標頭 access-control-allow-origin: *:

CORS 回應標頭

您需要對伺服器進行配置,僅允許一個來源提供,並阻止其他所有來源。

使用同樣的 cors Node 套件,以下是您應該如何執行:

const cors = require('cors')

const corsOptions = {
 origin: 'https://yourdomain.com',
}

app.get('/products/:id', cors(corsOptions), (req, res, next) => {
 //...
})

您也可以提供多個服務:

const whitelist = ['http://example1.com', 'http://example2.com']
const corsOptions = {
 origin: function (origin, callback) {
 if (whitelist.indexOf(origin) !== -1) {
 callback(null, true)
 } else {
 callback(new Error('CORS 不允許'))
 }
 },
}

預檢

有些請求以「簡單」方式處理。所有的 GET 請求屬於這個群組。

另外某些 POSTHEAD 請求也是如此。

如果滿足下列條件之一,POST 請求也屬於這個群組:

  • 使用 application/x-www-form-urlencoded Content-Type
  • 使用 multipart/form-data Content-Type
  • 使用 text/plain Content-Type

所有其他請求必須經過稱為預檢的預先核准階段才能執行。瀏覽器通過發送 OPTIONS 請求來確定是否有權限執行動作。

預檢請求包含一些標頭,伺服器將使用這些標頭檢查權限(省略無關的欄位):

OPTIONS /the/resource/you/request
Access-Control-Request-Method: POST
Access-Control-Request-Headers: origin, x-requested-with, accept
Origin: https://your-origin.com

伺服器將以如下的方式回應(省略無關的欄位):

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://your-origin.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE

我們檢查了 POST,但伺服器告訴我們可以使用其他 HTTP 請求類型來訪問該特定資源。

根據上述的 Node.js Express 範例,伺服器必須還處理 OPTIONS 請求:

var express = require('express')
var cors = require('cors')
var app = express()

//允許只有其中一個資源的 OPTIONS
app.options('/the/resource/you/request', cors())

//允許所有資源的 OPTIONS
app.options('*', cors())