一、跨域核心概念:同源策略与跨域定义
跨域问题的根源是浏览器的 同源策略(Same-Origin Policy),这是浏览器为保护用户数据安全而设置的核心安全限制。
1. 什么是 “同源”?
“同源” 指的是两个 URL 的 协议、域名、端口号 三者完全一致。只有满足同源条件,浏览器才允许 JS 脚本相互访问数据(如 AJAX 请求、操作 DOM 等)。
URL 示例 | 与 http://www.example.com:8080/index.html 是否同源 | 原因分析 |
---|---|---|
http://www.example.com:8080/about.html | 是 | 协议(http)、域名、端口完全一致 |
https://www.example.com:8080/index.html | 否 | 协议不同(http vs https) |
http://blog.example.com:8080/index.html | 否 | 域名不同(www vs blog,二级域名差异) |
http://www.example.com:80/index.html | 否 | 端口不同(8080 vs 80) |
http://www.example.org:8080/index.html | 否 | 主域名不同(example.com vs example.org) |
2. 什么是 “跨域”?
当 JS 脚本尝试访问 非同源 的资源(如发起 AJAX 请求、获取非同源页面的 DOM)时,就会触发浏览器的同源策略限制,这种场景称为 “跨域”。
常见跨域场景:
- 前端项目部署在
http://localhost:5500
,请求后端接口http://localhost:3000
(端口不同)。 - 本地开发时,请求线上接口(如前端
http://127.0.0.1
请求https://api.taobao.com
,域名不同)。
3. 同源策略的作用
同源策略并非 “限制”,而是 “保护”:
- 防止恶意网站通过 JS 读取用户在其他网站的 Cookie(如登录状态)。
- 防止恶意网站篡改非同源页面的 DOM,伪造用户操作。
- 避免敏感数据(如用户信息、支付数据)被未授权的脚本窃取。
二、跨域解决方案:前端与后端配合实现
解决跨域的核心思路是 “绕过或允许” 同源策略限制,常见方案分为 前端主导(如 JSONP)、后端主导(如 CORS)和 代理转发(如 Nginx 代理)三类。以下重点讲解前端常用的 JSONP 和后端配置的 CORS。
1. 方案一:JSONP(前端主导,仅支持 GET 请求)
JSONP(JSON with Padding)是早期解决跨域的经典方案,利用 <script>
标签不受同源策略限制的特性实现跨域请求。
1.1 JSONP 原理
<script>
标签的src
属性可以加载任意域名的资源(如 CDN 上的 JS 文件),浏览器不拦截。- 后端返回的不是纯 JSON 数据,而是 “回调函数名 ( JSON 数据)” 的 JS 代码。
- 前端提前定义好回调函数,当
<script>
加载并执行后端返回的 JS 代码时,会自动调用回调函数,从而获取数据。
1.2 JSONP 实现步骤(前端 + 后端)
(1)前端实现(以 “淘宝商品搜索建议” 为例)
html
预览
<!-- 输入框:用户输入关键词 -->
<input type="text" id="searchInput" placeholder="输入商品关键词">
<button id="searchBtn">搜索</button>
<ul id="suggestList"></ul><script>
// 1. 提前定义回调函数:接收并处理后端返回的数据
function handleSuggest(data) {const suggestList = document.getElementById("suggestList");suggestList.innerHTML = ""; // 清空旧数据// 渲染搜索建议(淘宝接口返回的 data.result 是二维数组)data.result.forEach(item => {const li = document.createElement("li");li.innerText = item[0]; // item[0] 是商品名称suggestList.appendChild(li);});
}// 2. 点击按钮发起 JSONP 请求
document.getElementById("searchBtn").onclick = function() {const keyword = document.getElementById("searchInput").value;if (!keyword) return;// 3. 创建 <script> 标签,通过 src 发起跨域请求const script = document.createElement("script");// 淘宝开放接口:cb 参数指定回调函数名(必须与前端定义的一致)script.src = `http://suggest.taobao.com/sug?code=utf-8&q=${encodeURIComponent(keyword)}&callback=handleSuggest`;// 4. 将 <script> 插入页面,触发请求document.body.appendChild(script);// 5. 请求完成后移除 <script> 标签(避免页面冗余)script.onload = function() {document.body.removeChild(script);};
};
</script>
(2)后端实现(PHP 示例)
若后端是自建接口(如 PHP),需配合返回 “回调函数包裹的 JSON”:
php
<?php
// 1. 获取前端传递的回调函数名(参数名通常为 callback)
$callback = $_GET['callback'];// 2. 准备要返回的数据(模拟商品搜索建议)
$data = ["code" => 200,"msg" => "success","result" => [["iPhone 15", "10000+ 销量"],["iPhone 15 Pro", "5000+ 销量"],["iPhone 15 壳", "20000+ 销量"]]
];// 3. 转换为 JSON 字符串
$jsonData = json_encode($data);// 4. 输出:回调函数名( JSON 数据 )
echo $callback . "(" . $jsonData . ")";
// 最终输出:handleSuggest({"code":200,"msg":"success",...})
?>
1.3 JSONP 的优缺点
优点 | 缺点 |
---|---|
兼容性好(支持所有浏览器,包括 IE) | 仅支持 GET 请求(无法发送 POST/PUT/DELETE) |
无需后端复杂配置(仅需返回特定格式) | 安全性低(可能遭受 XSS 攻击,需信任数据源) |
前端实现简单 | 无法捕获请求错误(如 404/500,<script> 加载失败无回调) |
2. 方案二:CORS(后端主导,支持所有 HTTP 方法)
CORS(Cross-Origin Resource Sharing,跨域资源共享)是 W3C 标准,也是目前解决跨域的 主流方案。它通过后端在响应头中添加特定字段,明确告知浏览器 “允许该域名跨域访问”,从而绕过同源策略限制。
2.1 CORS 原理
- 前端发起跨域请求时,浏览器会先发送一个 预检请求(OPTIONS 请求)(复杂请求,如 POST/PUT),询问后端 “是否允许当前域名访问”。
- 后端通过响应头(如
Access-Control-Allow-Origin
)告知浏览器允许的域名、方法、头部信息。 - 浏览器验证响应头后,若符合条件,则允许前端接收数据;否则拦截请求。
2.2 CORS 实现步骤(后端配置 + 前端 AJAX)
(1)后端配置(以 PHP 为例)
只需在后端接口中添加 CORS 响应头即可,核心字段如下:
php
<?php
// 1. 允许的前端域名(* 表示允许所有域名,生产环境建议指定具体域名)
header("Access-Control-Allow-Origin: http://localhost:5500");// 2. 允许的请求方法(GET/POST/PUT/DELETE 等)
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");// 3. 允许的请求头(如 Content-Type、Authorization)
header("Access-Control-Allow-Headers: Content-Type");// 4. 允许前端携带 Cookie(需配合 withCredentials,生产环境慎用)
// header("Access-Control-Allow-Credentials: true");// 5. 处理预检请求(OPTIONS 请求):直接返回 204 状态码
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {http_response_code(204);exit;
}// 6. 业务逻辑:返回数据
$response = ["code" => 200,"msg" => "CORS 跨域请求成功","data" => ["username" => "张三", "age" => 20]
];
echo json_encode($response);
?>
(2)前端 AJAX 请求(与普通 AJAX 无差异)
html
预览
<button id="corsBtn">发起 CORS 请求</button>
<div id="corsResult"></div><script>
document.getElementById("corsBtn").onclick = function() {const xhr = new XMLHttpRequest();// 跨域请求:前端 http://localhost:5500 请求后端 http://localhost:3000xhr.open("POST", "http://localhost:3000/api/user", true);// 设置请求头(需与后端 Access-Control-Allow-Headers 匹配)xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");// 发送请求(POST 参数)xhr.send("username=张三");// 处理响应xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {const result = JSON.parse(xhr.responseText);document.getElementById("corsResult").innerText = JSON.stringify(result, null, 2);}};
};
</script>
2.3 CORS 的优缺点
优点 | 缺点 |
---|---|
支持所有 HTTP 方法(GET/POST/PUT/DELETE) | 后端需额外配置(但配置简单,一次配置全局生效) |
安全性高(可精确控制允许的域名、方法) | 部分旧浏览器不支持(如 IE 8/9,需兼容时可用 JSONP) |
支持携带 Cookie(需配置 withCredentials) | 复杂请求会多一次预检请求(OPTIONS),轻微影响性能 |
3. 其他跨域方案(补充)
除了 JSONP 和 CORS,实际开发中还可能用到以下方案:
- Nginx 反向代理:前端请求本地 Nginx 服务器,Nginx 将请求转发到后端(因 Nginx 是服务器端,不受同源策略限制)。
- PostMessage:用于两个非同源页面之间的通信(如 iframe 父子页面),通过
window.postMessage()
发送数据,window.addEventListener("message")
接收数据。 - WebSocket:WebSocket 是全双工通信协议,一旦建立连接,不受同源策略限制(适合实时通信场景,如聊天、直播)。