引言:当浏览器说“不”,而 Postman 说“是”
想象一下这个场景:你正在用 JavaScript 编写前端代码,向 API 发送一个 fetch 请求。结果控制台里出现了一个红色错误:
Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.你抓耳挠腮,打开 Postman,输入相同的 URL,点击发送——然后 奇迹发生了,数据竟然返回了!服务器在工作吗?是的。那为什么浏览器会阻止这个请求呢?
这种情况是 Web 开发中的经典问题。在 Stack Overflow 上,这个问题已经积累了成千上万的浏览量。今天,我们将深入底层,弄清楚为什么会发生这种情况,CORS 是如何工作的,以及如何修复它。开始吧!
什么是 CORS,为什么需要它?
CORS(跨域资源共享) 是一种浏览器安全机制。它禁止来自一个网站(例如 http://localhost:3000)的 JavaScript 向另一个网站(例如 https://api.example.com)发送请求,除非服务器明确允许。
为什么需要它?为了防止攻击者通过你的浏览器从其他网站窃取数据。如果没有 CORS,任何脚本都可以读取你的邮件、银行账户——这太可怕了。
但这里有一个细微差别:CORS 仅在浏览器中生效。Postman、curl、服务器端请求(Node.js、Python)——它们不会检查 CORS。这是 Postman 能工作而 JavaScript 不能的首要原因。
CORS 底层工作原理:简单解释
当你的 JavaScript(例如 fetch)发出跨域请求时,浏览器会做两件事:
- 检查请求类型。
- 如果需要,发送一个预检请求(OPTIONS)。
简单请求 vs 预检请求
有两种类型的跨域请求:
- 简单请求——如果你使用 GET、POST 或 HEAD,并且只使用基本标头(Content-Type: text/plain, application/x-www-form-urlencoded, multipart/form-data)。浏览器会直接发送请求,然后检查响应头中的
Access-Control-Allow-Origin。 - 预检请求——如果你使用非标准标头(例如
Authorization)、PUT/DELETE 方法,或 Content-Type: application/json。浏览器会先发送一个 OPTIONS 请求来询问:“我可以发送这样的请求吗?”
以下是代码中的预检请求示例:
fetch('https://api.example.com/data', { method: 'POST', headers: { 'Content-Type': 'application/json', // 这会触发预检请求 'Authorization': 'Bearer my-token' }, body: JSON.stringify({ key: 'value' })}).then(response => response.json()).then(data => console.log(data)).catch(err => console.error('CORS error:', err));如果服务器没有在 OPTIONS 响应中返回正确的 Access-Control-Allow-Origin 标头——浏览器就会阻止主请求。
为什么 Postman 会忽略 CORS?
Postman 不是浏览器。它不使用同源策略。它只是发送一个 HTTP 请求并接收响应。它不关心请求来自哪里——它不会检查 CORS 标头。服务器可以返回任何内容,Postman 都会显示响应。
因此,当你通过 Postman 测试 API 时,你看到的是服务器在工作。但浏览器要求服务器明确允许从你的域名进行访问。
如何修复 CORS 错误:3 种实用方法
1. 配置服务器(正确的方法)
如果你有服务器的访问权限,请添加 Access-Control-Allow-Origin 标头。例如,在 Node.js 中使用 Express:
const express = require('express');const app = ex