在Express中处理CORS的方法

394 阅读4分钟

在浏览器中运行的JavaScript应用程序通常只能从提供这些资源的同一域(原点)访问HTTP资源。

从同一来源加载图像或脚本/样式总是有效的。此外,使用@font-face 加载网络字体时,默认设置了 "同源 "策略。其他不太流行的东西也是如此(比如Canvas API中加载的WebGL纹理和drawImage 资源)。

然而,对外部第三方服务器的XHR和Fetch调用将会失败。除非第三方服务器实现了一种机制,允许建立连接并下载和使用请求的资源。

这种机制被称为CORS,即跨源资源共享

需要 CORS 的一个非常重要的东西是 ES模块,最近在现代浏览器中被引入。

如果你没有在服务器上设置一个允许它为第三方来源提供服务的 CORS 策略,请求将失败。

Fetch例子。

Fetch failed because of CORS policy

XHR的例子。

XHR request failed because of CORS policy

一个跨原点资源如果是失败的。

  • 到一个不同的
  • 到一个不同的子域
  • 到一个不同的端口
  • 到一个不同的协议

CORS是为了你的安全而存在的,以防止恶意用户利用你碰巧使用的任何网络平台。

如果你同时控制着服务器客户端,你知道双方都是值得信赖的,因此有充分的理由允许资源共享。

怎么做?

这取决于你的服务器端堆栈。

浏览器支持

相当好(基本上除了IE<10以外的所有浏览器)。

CORS browser support

使用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: '😞 no CORS, no party!' })
})

const server = app.listen(3000, () => {
  console.log('Listening on port %s', server.address().port)
})

如果你用一个来自不同来源的获取请求打到/without-cors ,它就会引发CORS问题。

为了使事情顺利进行,你所需要做的就是需要上面链接的cors 包,并将其作为中间件函数传递给终端请求处理程序。

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

app.get('/with-cors', cors(), (req, res, next) => {
  res.json({ msg: 'WHOAH with CORS it works! 🔝 🎉' })
})

/* the rest of the app */

我做了一个简单的Glitch例子,这里是它的代码:https://glitch.com/edit/#!/flavio-cors-client

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

请注意,由于服务器没有正确处理 CORS 头信息而失败的请求,仍然被接收。正如你在网络面板上看到的,你可以看到服务器发送的信息。

No response from CORS

只允许特定的源头

然而,这个例子有一个问题。任何请求都会被服务器接受为跨来源。

正如你在网络面板上看到的,通过的请求有一个响应头access-control-allow-origin: *

The CORS response header

你需要对服务器进行配置,只允许一个原点的服务,而阻止其他所有的原点。

使用相同的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('Not allowed by CORS'))
    }
  },
}

预检

有一些请求是以一种 "简单 "的方式处理的。所有GET 的请求都属于这一组。

还有一些 POSTHEAD 的请求也是如此。

POST 如果它们满足了使用Content-Type of 的要求,则也属于这一组。

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

所有其他请求都必须经过一个预先批准的阶段,称为预检。浏览器这样做是为了确定它是否有执行一个动作的权限,方法是发出一个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()

//allow OPTIONS on just one resource
app.options('/the/resource/you/request', cors())

//allow OPTIONS on all resources
app.options('*', cors())