前言
前后端分离已成为现代web开发中的主流模式,前端页面通过JavaScript向后端API发起请求获取数据、访问资源等等。但是,访问资源、请求数据能够随意访问吗?或者说,随意访问后端资源会出现哪些问题?这就涉及到了现在前后端开发中一个极为重要的问题------------跨域问题。
一、跨域问题是啥?
1.1 定义
跨域问题就是指:浏览器出于安全考虑,阻止一个网站的 JavaScript 向另一个网站发送请求并获取数据。而要限制请求对数据的访问,则是依靠一个重要策略------同源策略(Same-Origin Policy)
1.2 啥是"同源"?
首先得知道"源"是什么。这里的"源"=协议+域名+端口号,那么,只要两个请求在这三项里面有一项不同,那就可以视为非同源请求 例如:
http://example.com:80与http://example.com:80------ 同源http://example.com:80与https://example.com:80------ 不同源(协议不同)http://example.com:80与http://api.example.com:80------ 不同源(主域不同)http://example.com:80与http://example.com:8080------ 不同源(端口不同)
为什么需要同源策略?
Web 的本质是开放,任何网站都可以被用户访问,如果不加以限制,A 网站的脚本就可以访问 B 网站的用户信息、发起恶意请求,造成数据泄露、权限滥用、CSRF 攻击 等一系列问题。因此,浏览器强制实施了同源策略(SOP) ,阻止不同源之间的资源访问和交互。
1.3 什么行为会被浏览器拦截?
以下行为如果发生跨域,会被浏览器拦截或受限:
- 通过 JavaScript 的
XMLHttpRequest/fetch请求外部 API - 读取非同源 iframe 的内容
- 操作
window.open()打开的非同源页面 - 设置或读取非同源 Cookie / Storage 数据
注意:标签加载资源(如 <img>, <script>, <link>)不会受到跨域限制,但这也可能带来潜在的安全隐患(例如CSRF、XSS)。
第二章:常见的跨域场景
2.1 本地开发中前后端分离
本地开发时,前端常运行在:
arduino
arduino
复制编辑
http://localhost:3000
而后端接口则部署在:
bash
bash
复制编辑
http://localhost:5000 或 http://api.example.com
前端通过 fetch('http://api.example.com/data') 请求数据时,由于端口或域名不同,属于跨域请求,浏览器将其视为潜在安全风险,可能拦截或限制请求行为。
2.2 多服务架构中的子域交互
例如:
- 前端页面部署在
https://www.example.com - 图片服务部署在
https://img.example.com - 用户服务部署在
https://user.example.com
它们虽然属于同一主域(example.com),但由于子域不同,依旧会被视为跨域。
2.3 第三方服务嵌入
如嵌入社交媒体组件、支付接口、广告投放脚本等,也属于跨域交互。
第三章:浏览器的跨域拦截机制详解
3.1 简单请求(Simple Request)
满足以下条件的请求被认为是"简单请求",浏览器会直接发出请求:
- 请求方法是:
GET,POST, 或HEAD - 请求头为浏览器自动添加的简单头部 (如
Accept,Content-Type为application/x-www-form-urlencoded,multipart/form-data, 或text/plain) - 没有使用自定义头部、Body 结构等复杂内容
浏览器行为:直接发出请求,但只能在目标服务允许的情况下读取响应。
3.2 预检请求(Preflight Request)
当请求不满足简单请求的条件,浏览器会先发送一个 OPTIONS 请求作为"预检",询问服务器是否允许真正的请求。
示例:
makefile
http
复制编辑
OPTIONS /data HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header
服务器需要响应:
makefile
http
复制编辑
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 86400
第四章:常用跨域解决方案
4.1 CORS(跨域资源共享)
最官方、最推荐的解决方案。需要服务器设置响应头来声明"我允许哪些来源的访问"。
服务端设置(Node.js + Express 示例):
javascript
js
复制编辑
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Allow-Credentials', 'true');
next();
});
如果使用框架,如Spring Boot、Django、ASP.NET等,也有对应的CORS配置。
4.2 开发代理(Dev Proxy)
前端构建工具如Webpack、Vite、Create React App 提供开发服务器代理配置,把请求转发给后端,从而绕过跨域限制。
Vite 配置示例:
css
js
复制编辑
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
rewrite: path => path.replace(/^/api/, '')
}
}
}
前端访问 /api/user 实际被代理为 http://localhost:5000/user
4.3 JSONP(已过时,仅支持GET)
通过 <script> 标签的跨域能力,动态创建脚本标签请求数据,并执行回调。
不推荐现代项目使用,原因如下:
- 仅支持
GET请求 - 存在XSS注入风险
- 数据格式不规范,难以调试
4.4 Nginx 反向代理
将 Nginx 设置为前后端统一入口,请求由 Nginx 中转,实现跨域绕过。
bash
nginx
复制编辑
location /api/ {
proxy_pass http://backend.example.com/;
proxy_set_header Host $host;
}
4.5 iframe + postMessage
用于跨域嵌套页面之间的通信:
- 父页面创建 iframe 嵌套其他域名
- 子页面通过
window.parent.postMessage()通知父页面 - 父页面用
window.addEventListener('message', handler)接收消息
适用于支付、嵌套服务等需要跨域通信的特殊场景。
第五章:跨域安全性分析
5.1 CORS 和 CSRF 的关系
CORS 本身不是一种防御机制,而是限制访问者获取跨域数据的机制 。如果服务器没有设置合理的 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials,容易被用于CSRF攻击。
应对方法:
- 服务端设置
SameSiteCookie 属性为Strict或Lax - 对关键接口使用 CSRF Token 双重校验机制
- 限制
Access-Control-Allow-Origin不使用通配符(*)
5.2 JSONP 的风险
JSONP 会将返回的数据作为 JavaScript 执行,如果攻击者伪造 callback 参数,可能导致恶意代码注入。
5.3 反向代理中的隐藏风险
如果代理规则配置错误,可能暴露不该访问的接口,例如:
- 管理员接口被前端访问
- 未登录用户访问需要认证的数据
第六章:实际开发中的最佳实践
6.1 本地开发时优先使用代理
- 使用前端工具配置代理
- 避免频繁修改后端 CORS 配置
- 适配本地与生产部署的差异
6.2 生产环境必须由服务端控制跨域
- 严格限定允许的域名
- 不使用
*通配符与 Cookie 共用 - 对敏感接口启用身份校验机制(如JWT、Session)
6.3 对iframe通信进行来源校验
javascript
js
复制编辑
window.addEventListener('message', (event) => {
if (event.origin !== 'https://trusted.com') return;
// 安全处理
});
第七章:前端框架与跨域
React
fetch默认不会携带 Cookie,需要设置credentials: 'include'- 使用 CRA(Create React App)时可以配置
setupProxy.js
Vue
- Vue CLI 提供
devServer.proxy配置 - Axios 默认不会携带 Cookie
ini
js
复制编辑
axios.defaults.withCredentials = true;
Angular
- 使用
HttpClient时可配置withCredentials选项
第八章:未来的发展与标准化趋势
随着浏览器对安全的关注度不断提升,跨域机制也在不断演化:
- Fetch API 与 Service Worker 支持更灵活的跨域请求控制
- COOP / COEP / CORP 安全头提升隔离性
- WebAssembly、WebGPU 等新技术也可能引入新的跨域安全模型
结语
跨域是前端开发中极其重要且不可忽视的问题。它并不是一种"错误",而是浏览器提供的安全保护机制。开发者应理解其原理,掌握常见场景与解决方案,在满足业务需求的同时保障系统安全。