针对JavaScript在Ajax或fetch请求中遇到的CORS跨域报错,系统解析同源策略机制、简单请求与预检请求判定,并给出服务端响应头配置、开发环境反向代理及排查验证的完整解决方案。
JavaScript Ajax请求遇到CORS跨域报错怎么解决
时间:2026-11-04
浏览:615
文章目录
当在前端页面使用 XMLHttpRequest 或 fetch 发起跨域网络请求时,如果控制台弹出 has been blocked by CORS policy 报错,核心原因是目标服务返回的 HTTP 响应头缺少浏览器要求的 CORS 许可声明。解决该问题的正规路径分为两种:在生产环境由后端或网关配置正确的 Access-Control-Allow-* 响应头;在本地开发环境则通过前端工程化开发服务器配置反向代理(Proxy)转接请求。
一、CORS 跨域报错的典型表现与底层机制
在前端控制台中最常见的报错提示如下:
Access to XMLHttpRequest at 'https://api.example.com/user/info' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
理解 CORS(Cross-Origin Resource Sharing,跨源资源共享)报错,必须先明确浏览器的同源判定准则与拦截执行时机:
1. 什么是同源(Same-Origin)
所谓“源”(Origin),由协议(Protocol)、域名或主机名(Host)、端口(Port)三部分共同组成。只有当这三者完全相同,浏览器才视其为同源。只要其中任何一项不一致,即属于跨域请求:
http://example.com与https://example.com(协议不同:HTTP vs HTTPS,跨域);http://example.com:8080与http://example.com:3000(端口不同,跨域);http://blog.example.com与http://api.example.com(二级域名不同,跨域);http://localhost:8080与http://127.0.0.1:8080(主机名不同,跨域)。
2. 核心事实:拦截发生在浏览器端,非服务端宕机
很多开发者误以为出现 CORS 错误是由于请求没有发送出去或服务端崩溃。事实恰恰相反:在多数情况下,Ajax 请求已经顺利通过网络到达后端,后端的业务逻辑与数据库更新也已经执行完毕并返回了 HTTP 200 响应。然而,当响应数据包返回到浏览器后,浏览器的同源安全引擎检查发现响应头中缺失 Access-Control-Allow-Origin 标识,出于保护用户隐私的同源策略,浏览器主动扣留了该数据包,禁止交付给前端 JavaScript 代码,并在控制台记录红字报错。
二、排查第一步:区分简单请求与预检请求(Preflight)
在定位具体为何被拦截前,需要确认该请求属于“简单请求”还是“预检请求”。W3C 规范对两者的处理方式存在显著差异:
| 请求分类 | 触发条件 | 浏览器处理行为 |
|---|---|---|
| 简单请求 | 方法为 GET、POST 或 HEAD;只包含基础请求头(如 Accept、Accept-Language);Content-Type 仅限 application/x-www-form-urlencoded、multipart/form-data 或 text/plain。 |
浏览器直接向服务端发出正式请求,在收到响应后直接核验 Access-Control-Allow-Origin。 |
| 预检请求(复杂请求) | 使用了 PUT、DELETE、PATCH 方法;或者携带了自定义请求头(如 Authorization、X-Token);或者 Content-Type 为 application/json。 |
浏览器在发送正式业务请求前,会自动发出一个 OPTIONS 请求。只有当 OPTIONS 请求收到合规的 CORS 许可后,才接着发送正式业务请求。 |
现代化 Web 应用多采用前后端分离架构,前端 Ajax 提交数据普遍采用 Content-Type: application/json 或在 Header 中携带 JWT Token,因此绝大多数请求都会触发 OPTIONS 预检请求。如果服务端的路由网关或拦截器未能正确放行 OPTIONS 请求(如直接拦截并返回了 401 或 403),真正的业务请求甚至根本没有机会发出。
三、生产环境标准解决方案:服务端配置 CORS 响应头
解决跨域的根本途径在于目标服务端明确告知浏览器允许哪些源发起访问。以下梳理主流技术栈的具体配置操作:
1. Node.js (Express / Koa) 环境配置
在 Express 中,最稳妥的做法是引入官方维护的 cors 中间件,并显式指定来源白名单:
const express = require('express');
const cors = require('cors');
const app = express();
// 允许的前端源白名单列表
const allowlist = ['http://localhost:3000', 'https://webapp.example.com'];
const corsOptionsDelegate = (req, callback) => {
let corsOptions;
const origin = req.header('Origin');
if (allowlist.indexOf(origin) !== -1) {
// 明确回显允许的 Origin,并支持携带有状态凭据
corsOptions = {
origin: origin,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
maxAge: 86400 // 缓存预检结果 24 小时
};
} else {
corsOptions = { origin: false };
}
callback(null, corsOptions);
};
app.use(cors(corsOptionsDelegate));
// 必须确保业务接口在 CORS 中间件之后挂载
app.get('/api/user', (req, res) => {
res.json({ code: 200, message: '请求成功' });
});
app.listen(8080);
2. Nginx 反向代理配置(推荐微服务与网关层)
在统一反向代理层配置,可以一次性解决下游多个微服务的跨域支持。需要格外注意对 OPTIONS 请求直接返回 HTTP 204:
server {
listen 80;
server_name api.example.com;
location / {
# 拦截 OPTIONS 预检请求直接应答
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $http_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' 86400 always;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# 正式业务请求添加 CORS 响应头
add_header 'Access-Control-Allow-Origin' $http_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
proxy_pass http://127.0.0.1:8080;
}
}
3. Java Spring Boot 环境配置
在 Spring Boot 中,全局配置通常实现 WebMvcConfigurer 接口:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// 使用 allowedOriginPatterns 支持细粒度域名校验
.allowedOriginPatterns("http://localhost:3000", "https://*.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
四、本地开发环境解决方案:配置开发服务器代理(Proxy)
在前后端联调的本地开发阶段,若后端开发人员尚未部署完毕 CORS 逻辑,前端开发者可通过构建工具自带的 Node.js 开发服务器搭建正向代理规避跨域问题。
原理说明:同源策略是浏览器强加在 Web 前端的安全沙箱机制。服务器与服务器之间的 HTTP 通信(例如 Node 开发服务器与后端的 Spring/Go 接口通信)并不经过浏览器同源引擎,因此天然不存在跨域问题。
1. Vite 工程化项目配置(vite.config.js)
import { defineConfig } from 'vite';
export default defineConfig({
server: {
port: 3000,
proxy: {
// 当请求路径以 /api 开头时触发代理
'/api': {
target: 'https://api.example.com', // 后端真实接口基础地址
changeOrigin: true, // 修改请求头中的 Host 为目标服务器地址
rewrite: (path) => path.replace(/^\/api/, '') // 根据需要重写路径
}
}
}
});
2. 前端请求代码书写要求
配置代理后,前端调用 Ajax 时不可再把远端绝对域名写死在代码中,而必须改用同源的相对路径:
// 错误写法:直接调用远端,依旧走浏览器跨域检查,代理不生效!
// fetch('https://api.example.com/user/info');
// 正确写法:调用本地相对路径,由 Vite 开发服务器拦截并代发
fetch('/api/user/info', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
}
})
.then(res => res.json())
.then(data => console.log('数据接收成功:', data));
五、高频踩坑场景与具体排查清单
*The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.原因与解法:当发起请求携带了凭据(前端设置
credentials: 'include' 或 withCredentials = true)时,W3C 规范要求 Access-Control-Allow-Origin 必须是具体的精确域名(例如 http://localhost:3000),绝对不允许设置为 *。后端应读取请求头中的 Origin 并在白名单匹配后动态原样回显。
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.(HTTP 401 或 403)原因与解法:预检 OPTIONS 请求是浏览器自发机制,绝不携带任何业务 Cookie 或 Authorization 头。若后端的 Spring Security、Shiro 或 JWT 拦截器对所有接口一律要求验签,则 OPTIONS 请求必会被判定为未登录而返回 401/403。解决办法是:在鉴权拦截器的首行加入判定:如果
request.getMethod().equalsIgnoreCase("OPTIONS"),直接放行返回 204。
The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:3000, http://localhost:3000', but only one is allowed.原因与解法:通常发生在 Nginx 反向代理中配置了
add_header Access-Control-Allow-Origin,而上游的 Java/Node.js 代码中同时也配置了 @CrossOrigin。两个响应头叠加返回,导致浏览器无法识别单一合法源。排查时应明确职责边界,只保留链路中一层(建议优先在网关/Nginx 层或单体应用框架层二选一)注入响应头。
JSONP(只支持 GET 请求且存在严重 XSS 风险)之外,纯浏览器前端无法在不借助任何服务端(目标服务端、反向代理或自建中间层)的情况下绕过 CORS。诸如在前端请求头中自行添加 Access-Control-Allow-Origin 是毫无作用的,因为该字段是只读的响应头,必须由提供数据的服务端负责返回。
六、如何验证跨域问题已真正修复
在完成配置后,请按照以下步骤在浏览器的开发者工具中进行验证,确认链路已彻底健康:
- 打开 Chrome / Edge 浏览器的 DevTools(按
F12),切换到 Network(网络) 面板; - 勾选 Preserve log(保留日志) 和 Disable cache(停用缓存);
- 触发你的前端 Ajax / fetch 动作,在请求列表中观察:
- 若有预检请求,列表中会首先出现一个同名但 Method 为
OPTIONS的条目,其 Status 必须为200或204; - 点击该条目,检查 Response Headers,确认包含
Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers字段; - 随后出现的实际业务请求(如
POST /api/user),其 Status 状态码应为200 OK,并在 Response 面板中可正常查看到返回的 JSON 数据结构;
- 若有预检请求,列表中会首先出现一个同名但 Method 为
- 切换至 Console(控制台) 面板,确认红色的
blocked by CORS policy报错已彻底消失,JavaScript 代码已能成功解构使用返回值。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。