CORS 中间件适用于 app.get 但不适用于 app.post

Posted

技术标签:

【中文标题】CORS 中间件适用于 app.get 但不适用于 app.post【英文标题】:CORS middleware works for app.get but not app.post 【发布时间】:2021-03-04 21:34:55 【问题描述】:

使用以下代码时:

服务器:

const cors = require('cors')
const mongoose = require('mongoose');
const Pet = mongoose.model('pets');

const corsOptions = 
origin: 'http://localhost:3000'

app.get(`/api/pets`, cors(corsOptions), async (req, res) => 
    let pets = await Pet.find();
    return res.status(200).send(pets);
);

app.post(`/api/pets`, cors(corsOptions), async (req, res) => 
    let pet = await Pet.create(req.body);
    return res.status(201).send(
        error: false,
        pet
    )
)

客户:

import axios from 'axios';

export default 
    getAll: async () => 
        let res = await axios.get(`http://localhost:5000/api/pets`);
        return res.data || [];
    ,
    post: async (name, birthdate, decription) => 
    let res = await axios.post(`http://localhost:5000/api/pets`, 
        name: name,
        birthdate: birthdate,
        description: decription
    );
        return res;
    ,
    ...

客户端可以正常访问 .get 路由,但是当它尝试 .post 时出现以下错误:

“从源“http://localhost:3000”访问“http://localhost:5000/api/test”的 XMLHttpRequest 已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查: 请求的资源上不存在“Access-Control-Allow-Origin”标头。”

Picture of headers

但是,这将允许 .post 路由正常工作:

服务器:

const cors = require('cors')
...

const corsOptions = 
origin: 'http://localhost:3000'

app.use(cors(corsOptions));

app.get(`/api/pets`, async (req, res) => 
    ...
);

app.post(`/api/pets`, async (req, res) => 
    ...
)

这两种方法有什么区别,为什么使用“app.use(...)”允许 .post 路由工作,而另一个不能工作?

【问题讨论】:

您的 POST 请求中可能有一些东西触发了您没有处理程序的飞行前请求(OPTIONS 请求)。如果您希望我们帮助您,我们需要查看 POST 请求的确切内容,包括内容类型和自定义标头。 我已经编辑了帖子以包含我所拥有的内容,并且我还添加了客户端的内容。 “宠物”是猫鼬模型 您是否有任何具体原因希望仅在某些 URL 上使用 CORS 而在其他 URL 上不使用? 因为我只希望它接受来自该 URL 的请求。我希望有一天能部署它,我希望它至少有一些安全性 我们需要查看从客户端发送请求的完整代码或实际的 POST 请求(包括所有标头)。正如我上面所说,我怀疑你在 POST 请求中有一些东西会触发你没有处理程序的 CORS 预检。但是,只看到代码的外壳而没有实际查看请求本身意味着我们无法确定。请求上的某些内容类型或某些标头可以触发预检。仅供参考,如果您查看 Chrome 调试器中的网络选项卡,您可以确切地看到正在发生的事情。我们现在都瞎了眼。 【参考方案1】:

您必须在允许的http methods 中明确允许方法发布。

所以你的 cors 选项应该是这样的:

const corsOptions = 
  "origin": "http://localhost:3000",
  "methods": "GET,POST", //here explicitly allowing http post method
  "preflightContinue": false,
  "optionsSuccessStatus": 204

这里是cors lib 的文档,您可以查看更多选项。

【讨论】:

显式添加 POST 方法并没有解决问题 你能用代码的当前状态更新你的代码吗?【参考方案2】:

所有现代浏览器都会发送飞行前请求(在发送 post 或 get 请求之前发送的请求)以实施同源策略。服务器应该回复可以访问已发送响应的域。 在第一种情况下,您没有在响应中发送任何飞行前响应。 当你使用app.use(cors(corsOptions));时,你的express服务器会在pre-flight request中添加"origin": "*",这意味着所有域都可以访问服务器发送的响应。 至于为什么 get 有效而 post 无效:任何请求(包括任何 GET 请求)包含一个不在上面列出的 CORS 安全列表请求标头中的标头,都将触发预检。 这意味着您的 get 请求标头可能是 CORS 安全列表中的请求标头。

更多信息可以在这里找到:Preflight request is sent with all methods

这里:https://medium.com/@dtkatz/3-ways-to-fix-the-cors-error-and-how-access-control-allow-origin-works-d97d55946d9

我正在添加其他信息来解释为什么第一种情况不起作用而第二种情况起作用:

首先让我说一下,何时发送 CORS 请求。 CORS 请求(或预检请求)仅在请求不是“简单请求”时发送,您可以看到简单请求是什么here。

现在,每当浏览器遇到一个不是简单请求的请求时,它会在发送实际请求(get/post 或任何其他方法)之前发送一个预检请求(HTTP 方法:选项)。在第一种情况下,get 可以正常工作,因为它属于“简单请求”类别。但是 post 不起作用,因为浏览器会在发送实际的 POST 请求之前发送一个 OPTIONS 预检请求(因为 POST 不是一个简单的请求 - 请参阅上面的链接了解原因)。由于在第一种情况下您只为 post 和 get 实现了 cors,因此服务器不处理 OPTIONS 请求,因此浏览器不会发送 POST 请求。

在第二种情况下,您使用了 app.use(),它将在服务器接收到的任何请求中包含 CORS 标头,因此您的预检 OPTION 请求由 app.use() 处理,它允许 localhost:3000 作为产地。

【讨论】:

你真的认为所有的浏览器都会在所有的 POST 请求上发送 pre-flight 吗?你帖子的那部分对我来说似乎不合适。 但我在 corsOptions 中将我的起源定义为 const corsOptions = origin: 'localhost:3000' @Jefumaru 我已经编辑了回复,看看它是否解释了事情,如果可以,我会更好地格式化它。 所以 POST 不是一个简单的请求...这意味着它不应该触发预检请求...对吗? No.. 因为 POST 不是一个简单的请求,它会触发预检。这就是我提到的。

以上是关于CORS 中间件适用于 app.get 但不适用于 app.post的主要内容,如果未能解决你的问题,请参考以下文章

CORS 适用于 chrome 但不适用于 firefox、angularjs

使用 CORS 与 LocomotiveJs 一起休息 API。适用于本地机器,但不适用于 Amazon 或 Heroku

Angular CORS 适用于 GET 但不适用于 DELETE

CORS 问题:C# API 请求适用于一个域,但不适用于另一个域

Amazon S3 CORS 适用于 HTTP,但不适用于 HTTPS

Go Restful API:CORS 配置适用于 React 但不适用于 Angular