介紹跨域資源共享,一種讓客戶端和服務器通信的方式,即使它們不在同一個域
在瀏覽器中運行的JavaScript應用程序通常只能訪問提供它的相同域名(源)上的HTTP資源。
加載圖片、腳本和樣式始終可以正常工作,但對另一個服務器的XHR和Fetch調用將失敗,除非該服務器實現了一種方法來允許該連接。
這種方法稱為CORS(Cross-Origin Resource Sharing,跨域資源共享)。
默認情況下,使用@font-face
加載Web字體也需要跨域資源共享,還有其他一些不太常見的情況(例如WebGL紋理和Canvas API中加載的drawImage
資源)。
在現代瀏覽器中,使用ES模塊(ES Modules)也需要CORS。
如果您沒有在服務器上設置允許提供第三方域的CORS策略,則請求將失敗。
Fetch示例:
XHR示例:
如果跨域資源違反以下條件,則會失敗:
- 不同的域名
- 不同的子域名
- 不同的端口
- 不同的協議
這樣做是為了您的安全,以防止惡意用戶利用Web平台。
但是,如果您控制服務器和客戶端,則有足夠的理由讓它們互相通信。
如何實現呢?
這取決於您的服務器端堆棧。
瀏覽器支持
非常好(基本上所有瀏覽器,除了IE<10):
使用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標頭而失敗的請求仍然被接收,您可以在其中找到服務器發送的消息:
只允許特定的域
但是這個例子有個問題:服務器將接受任何請求作為跨域請求。
如您在Network面板中可以看到,通過測試的請求響應標頭中有access-control-allow-origin: *
:
您需要配置服務器以僅允許一個源來提供服務,並阻止其他所有源的服務。
使用相同的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
請求。
還有一些的POST
和HEAD
請求也是如此。
如果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())