← 返回工具首页 CORS 报错?我是这么解决的

CORS 报错?我是这么解决的

部署完代码,满心欢喜打开页面,结果控制台一抹红——

Access to fetch at 'https://api.example.com' from origin 'https://your-site.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

熟悉吗?这玩意儿几乎每个前端工程师都遇到过。我也不例外。刚入行那会儿,每次看到这个报错就头皮发麻,来回改配置、重启服务,折腾半天。后来踩的坑多了,才摸索出一套"一看报错就知道哪儿的毛病"的思路。今天就把这些经验摊开说说,不讲废话,直接实战。

一、先说真实报错场景,你属于哪种?

CORS 报错长这样,但不完全一样。浏览器控制台里最常看到的几种:

1. 没有任何 CORS 头,直接拒绝访问

Access to fetch at 'https://api.example.com' from origin 'https://your-site.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

这是最典型的——后端压根没配任何 CORS 响应头。

2. 用了通配符 *,但带了 credentials

Access to fetch at 'https://api.example.com' from origin 'https://your-site.com' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header must be '*' (wildcard) when the request's credentials mode is 'include'.

后端配了 Access-Control-Allow-Origin: *,但前端 fetch() 里带着 credentials: 'include',浏览器不干。这俩是互斥的,不能一起用。

3. 预检请求过了,但实际请求还是失败

Access to XMLHttpRequest at 'https://api.example.com' from origin 'https://your-site.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It doesn't pass accessibility check.

说明后端给 OPTIONS 请求回了响应头,但实际响应里缺少某些头,比如 Access-Control-Allow-Credentials: true

你是哪种?先认清楚报错信息,再往下走,能省很多弯路。

二、CORS 到底是个什么东西?

2.1 同源策略(SOP)——根本原因

浏览器有个铁规则,叫同源策略(Same-Origin Policy)。简单说:一个网页只能"顺理成章"地访问同一个源的资源。这里的"源"指的是:

三个都一样,叫同源;有任何一个不一样,叫跨域。跨域请求会被浏览器直接拦下来——注意是浏览器拦,不是后端拦。也就是说:后端其实收到了请求、也返回了数据,但浏览器一看响应头里没有 CORS 相关字段,直接把响应废弃了,你的 JS 代码拿不到数据。

这就是为什么:本地开发没问题,部署到服务器就报错了。因为本地开发时前端和后端往往跑在同一台机器的同一个端口下,是同源的。

2.2 预检请求(Preflight Request)——很多人翻车的地方

浏览器把跨域请求分两类:简单请求非简单请求

简单请求必须同时满足:

简单请求直接发出去,服务端正常处理就行。

非简单请求:任何自定义头、PUT/DELETE 方法、JSON 格式的 POST(Content-Type: application/json)等,都属于非简单请求。浏览器会先发一个 OPTIONS 预检请求,等后端确认"行,我允许你访问",才发真正的请求。

# 预检请求的样子 OPTIONS /api/user HTTP/1.1 Origin: https://your-site.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type, Authorization
很多人不知道 OPTIONS 请求这回事。后端没配 OPTIONS 的处理逻辑,预检直接失败,后面所有请求都发不出去。所以后端配置 CORS 时,必须同时处理 GET 和 OPTIONS 两种方法,很多踩坑就踩在这儿。

2.3 核心响应头,一个一个说

CORS 的机制全靠几个响应头撑起来:

三、后端怎么配?三大主流语言全覆盖

3.1 Node.js + Express

这是前端圈最常见的组合。推荐用 cors 中间件,别自己手写:

const express = require('express'); const cors = require('cors'); const app = express(); // 最简单:允许所有 app.use(cors()); // 生产环境建议:指定具体域名 app.use(cors({ origin: 'https://your-site.com', // 只允许这个源 methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, // 允许 Cookie maxAge: 86400 // 缓存预检结果 24 小时 }));

需要手写中间件的话(没装 cors 包),这样写:

const express = require('express'); const app = express(); app.use('/', (req, res, next) => { res.setHeader('Access-Control-Allow-Origin', 'https://your-site.com'); res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization'); res.setHeader('Access-Control-Allow-Credentials', 'true'); // 预检请求直接返回,不往下走了 if (req.method === 'OPTIONS') { return res.sendStatus(204); } next(); });

3.2 Python + Flask

Flask 推荐用 flask-cors 扩展,一行搞定:

from flask import Flask, jsonify from flask_cors import CORS app = Flask(__name__) # 一行解决所有 CORS 问题 CORS(app, origins="https://your-site.com", supports_credentials=True) # 或者更细粒度地配置 # CORS(app, resources={r"/api/*": {"origins": "https://your-site.com"}}, supports_credentials=True) @app.route('/api/user', methods=['GET', 'POST']) def get_user(): return jsonify({"name": "xsanye", "role": "developer"}) if __name__ == '__main__': app.run(port=5000)

不想用扩展,手写中间件:

from flask import Flask, jsonify, make_response app = Flask(__name__) def add_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = 'https://your-site.com' response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization' response.headers['Access-Control-Allow-Credentials'] = 'true' return response # OPTIONS 预检路由 @app.route('/api/<path:path>', methods=['OPTIONS']) def cors_preflight(path): resp = make_response('', 204) return add_cors_headers(resp) # 所有其他路由后加上 CORS 头 @app.after_request def apply_cors(response): return add_cors_headers(response)

3.3 Java + Spring Boot

Spring Boot 有两种配法。第一种,全局配置类:

import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("https://your-site.com") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("Content-Type", "Authorization") .allowCredentials(true) .maxAge(3600); } }

第二种,用注解,更精细控制:

import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api") public class ApiController { @CrossOrigin(origins = "https://your-site.com", credentials = true) @GetMapping("/user") public Map<String, String> getUser() { return Map.of("name", "xsanye", "role", "developer"); } }

四、前端怎么调试?工具和方法

4.1 先判断是简单请求还是非简单请求

打开浏览器 Network 面板,找这个请求:

灰色 OPTIONS 状态码不是 200/204,而是 4xx——问题就在这儿,说明后端没处理预检。

4.2 检查响应头

在 Network 面板点开任意一个请求,看 Response Headers 里有没有:

没有?说明后端没配或者配置没生效。

4.3 前端带 credentials 时怎么写

// 带 Cookie 的跨域请求 fetch('https://api.example.com/api/user', { method: 'POST', credentials: 'include', // 带上 Cookie headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer xxx' }, body: JSON.stringify({ name: "xsanye" }) }).then(r => r.json()).then(console.log);

注意:后端必须把 Access-Control-Allow-Origin 设置为具体域名,不能是 *,否则浏览器会拒绝 credentials 请求。

4.4 开发环境用代理,服务环境用后端配置

本地开发时,前端跑在 localhost:3000,后端跑在 localhost:4000,天然跨域。最舒服的做法是用代理,让本地 dev server 替你转发:

// vite.config.js(Vue 项目) export default { server: { proxy: { '/api': { target: 'http://localhost:4000', // 代理到后端地址 changeOrigin: true, secure: false } } } }; // 或 package.json 中配置(Create React App) // "proxy": "http://localhost:4000"
// express 项目 dev server 代理中间件 const { createProxyMiddleware } = require('http-proxy-middleware'); app.use('/api', createProxyMiddleware({ target: 'http://localhost:4000', changeOrigin: true }));

4.5 用在线 CORS 测试工具快速验证

不确定自己的后端 CORS 配得对不对?用工具直接测:CORS 测试工具,输入 URL 看返回的响应头对不对,比改代码再重启服务快多了。

五、JSONP vs CORS——别再用 JSONP 了

有些老项目还在用 JSONP 绕 CORS,这儿说一句:JSONP 是用 <script> 标签绕过同源限制,本质上是个漏洞——它要求后端返回一个 JS 函数调用,而 JS 函数调用的参数是数据。这玩意儿:

所以,别用 JSONP。老老实实配 CORS

六、开发环境和生产环境的区别

很多人问:本地开发好好的,怎么上线就挂了?

原因很简单:本地开发时前后端同源(都是 localhost),根本不存在跨域问题。上线后,前端在 https://your-site.com,后端在 https://api.example.com,跨域了,CORS 才开始生效。

所以:

七、常见误区,逐条说清楚

误区1:后端配了 CORS,浏览器还是报跨域错误

检查是不是只配了 GET,没配 OPTIONS。预检请求如果没被正确处理,后续请求发不出去。另外,确认你的请求是从浏览器发出来的——命令行 curl 不受同源策略限制,看起来"通了",但浏览器里还是报错。

误区2:把 CORS 配置写到 Nginx 反向代理里就行了

Nginx 只是个代理,它加的响应头是在转发过程中加的,不会影响浏览器对跨域的判断。确认你的后端服务本身正确处理了 CORS,Nginx 只做它该做的事。

误区3:credentials 和通配符可以一起用

不行。浏览器的规定:带 credentials 的请求,Access-Control-Allow-Origin 必须是具体域名,不能是 *。两边都配了的话,浏览器会直接拒绝请求。

误区4:CORS 配置可以临时生效,上线再改

不要这样。开发阶段就配好 CORS,特别是预检请求的处理。上线临时改很容易漏掉 OPTIONS 的处理,导致部分请求失败。

误区5:用 JSONP "解决" CORS 问题

JSONP 是老技术,有安全风险,现代项目不要再用。直接配 CORS,一个中间件搞定。

总结

CORS 这事儿,搞清楚了原理,其实不复杂:

遇到 CORS 报错,先看控制台报错信息,再打开 Network 面板看响应头,按图索骥定位到具体问题。90% 的情况都是后端没配或漏了 OPTIONS 的处理,剩下的 10% 是 credentials 和通配符冲突。知道这两条,基本能解决所有 CORS 问题。

实用工具推荐

在线测试 CORS 响应头、检查跨域配置是否正确——
不需要安装,打开就能用。

立即使用 CORS 测试工具 →

💡 遇到同类问题?用工具快速解决

试试这些配套工具,无需注册,打开即用

浏览所有工具

常见问题

Q: 如何使用 cors报错解决方法实战指南 相关工具?
A: 这类工具一般有明确的输入框和输出框,按提示输入内容,点击对应按钮即可得到结果。建议先用简单示例测试功能是否正常,再处理实际数据。
Q: cors报错解决方法实战指南 适合在什么场景使用?
A: 根据具体工具类型决定。格式转换工具适合处理第三方数据,编码工具适合加密传输,压缩工具适合文件上传前处理。多积累工具使用经验,遇到问题时能快速判断用哪个工具解决。
Q: 有没有更好的替代工具?
A: 不同工具有不同侧重,重点是理解原理。可以同时安装多个类似工具,实际使用中对比效果,选择最顺手的一个。随着使用经验增加,你也能判断工具的好坏。