js 跨域是什么原因怎么解决
浏览器同源策略要求协议、域名、端口完全一致,任一不同即跨域,且请求已发出、服务端已返回,只是响应被浏览器拦截;应通过服务端正确配置 CORS 响应头,开发用 devServer 代理、生产用 Nginx 反向代理解决。
跨域不是接口写错了,而是浏览器同源策略在响应回来时主动拦截。结论:JS 跨域的根因是浏览器要求「同源」,只要协议、域名、端口三者有一个不同就会被拦截;正解是服务端返回正确的 CORS 响应头,开发阶段用 devServer 代理,生产环境用 Nginx 反向代理把跨域变成同源。
跨域是什么原因造成的?
结论:跨域的判定标准是协议、域名、端口三者必须完全一致,任何一项不同都算跨域,与服务器是否正常返回数据无关。
http://localhost:8080 请求 https://localhost:8080 跨域(协议不同);http://a.com 请求 http://a.com:81 跨域(端口不同);http://localhost 请求 http://127.0.0.1 跨域(域名不同,哪怕指向同一台机器)。
关键点在于:请求其实已经发出去了,服务端也正常执行并返回了 200,只是浏览器在把响应交给 JS 之前把它拦掉了。所以你在 Network 面板能看到 200 和完整响应体,控制台却报 blocked by CORS policy。理解这一点很重要——跨域从来不是「请求发不出去」,而是「响应读不到」。
什么时候会触发 OPTIONS 预检请求?
结论:只有「非简单请求」才会先发一次 OPTIONS 预检,简单请求直接发出,不预检。
简单请求必须同时满足三条:方法为 GET、HEAD、POST;Content-Type 只能是 application/x-www-form-urlencoded、multipart/form-data、text/plain 三种;没有自定义请求头。
实际项目里只要用了 Content-Type: application/json 的 POST,或者加了 Authorization、X-Token 这类自定义头,就必然触发预检。预检请求需要服务端正确响应 Access-Control-Allow-Methods 和 Access-Control-Allow-Headers,否则真实请求根本不会发出。用 Access-Control-Max-Age: 7200 可以缓存预检结果,Chrome 对该值上限为 7200 秒(2 小时)。
服务端怎么配置 CORS 才正确?
结论:CORS 的核心是服务端返回 Access-Control-Allow-Origin,如果携带 Cookie,该值必须是具体域名而不能是 *,并同时返回 Access-Control-Allow-Credentials: true。
Nginx 反向代理层加头的写法:
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 'Content-Type,Authorization,X-Token' always;
if ($request_method = OPTIONS) { return 204; }
注意 always 参数,不加的话 4xx/5xx 响应不会带上 CORS 头,前端看到的会是一个误导性的跨域错误,而不是真实的 500。
Spring Boot 项目可以用 @CrossOrigin(origins = "https://a.com", allowCredentials = "true"),或全局注册 WebMvcConfigurer#addCorsMappings。Node/Express 用 cors 中间件,配置 origin: 'https://a.com', credentials: true。
前端开发环境怎么绕过跨域?
结论:本地开发用构建工具的 proxy 把请求转发到后端,浏览器只看到同源请求,这是开发期最省事的方案。
Vite 在 vite.config.js 里配置:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: p => p.replace(/^\/api/, '') } } }
webpack devServer 的 devServer.proxy 写法基本一致,changeOrigin: true 必须开,否则目标服务器收到的 Host 头还是前端地址,部分框架会拒绝。代理只在 npm run dev 时生效,打包后的静态文件不受它管。
生产环境用 Nginx 同域部署:前端 location /,接口 location /api/ { proxy_pass http://backend; },这样浏览器眼里全是同源,压根不产生跨域。
JSONP 和 document.domain 还能用吗?
结论:都不推荐。JSONP 只支持 GET、有 XSS 风险;document.domain 已在 Chrome 115 起被移除,现代浏览器不再支持。
父子 iframe 跨域通信现在用 postMessage 配合 event.origin 白名单校验。如果是要用 SharedArrayBuffer 或高精度计时器,则需要服务端下发 Cross-Origin-Opener-Policy: same-origin 和 Cross-Origin-Embedder-Policy: require-corp 开启跨域隔离。
几个高频踩坑点
- 带 Cookie 时前端要设
withCredentials: true(fetch 用credentials: 'include'),后端Access-Control-Allow-Origin不能是*,两者必须配套。 - 后端抛异常直接返回 500、没走 CORS 过滤器时,前端只会看到跨域报错,排查时先看后端日志。
- 本地
localhost和127.0.0.1是不同源,混用会莫名报跨域。 - 只在响应头加
Access-Control-Allow-Origin却不处理 OPTIONS,预检失败会导致所有带自定义头的请求全挂。
跨域本质是浏览器的安全边界,不是要「破解」它。开发期交给 proxy,上线后交给 Nginx 同域或后端 CORS 头,两条路选一条走通即可;剩下的 JSONP、document.domain 属于历史包袱,新项目不要再碰。
转载请注明出处,版权归原作者所有。