介紹跨域資源共享,一種讓客戶端和服務器通信的方式,即使它們不在同一個域

在瀏覽器中運行的JavaScript應用程序通常只能訪問提供它的相同域名(源)上的HTTP資源。

加載圖片、腳本和樣式始終可以正常工作,但對另一個服務器的XHR和Fetch調用將失敗,除非該服務器實現了一種方法來允許該連接。

這種方法稱為CORS(Cross-Origin Resource Sharing,跨域資源共享)。

默認情況下,使用@font-face加載Web字體也需要跨域資源共享,還有其他一些不太常見的情況(例如WebGL紋理和Canvas API中加載的drawImage資源)。

在現代瀏覽器中,使用ES模塊(ES Modules)也需要CORS。

如果您沒有在服務器上設置允許提供第三方域的CORS策略,則請求將失敗。

Fetch示例:

由於CORS策略,Fetch請求失敗

XHR示例:

由於CORS策略,XHR請求失敗

如果跨域資源違反以下條件,則會失敗:

  • 不同的域名
  • 不同的子域名
  • 不同的端口
  • 不同的協議

這樣做是為了您的安全,以防止惡意用戶利用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('已監聽端口 %s', server.address().port)
})

如果您從不同的源發起fetch請求並使用中間件函數來處理經過響應的端點請求處理程序,則可以使事情正常工作:

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

請注意,在Network面板中,您可以看到由於未正確處理CORS標頭而失敗的請求仍然被接收,您可以在其中找到服務器發送的消息:

無CORS響應

只允許特定的域

但是這個例子有個問題:服務器將接受任何請求作為跨域請求。

如您在Network面板中可以看到,通過測試的請求響應標頭中有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請求滿足以下條件中的一個,也屬於這個組別,可以繞過預檢請求:

  • 使用以下Content-Type之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

所有其他請求都必須通過預檢請求(preflight)才能進行。瀏覽器通過發送一個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())