同源策略
定义
当两个URL的协议Protocol、域名Host、**端口Port**完全相同时,视为同源。
示例:https://www.example.com:443/page的同源判断
- ✅
https://www.example.com:443/home(同源) - ❌
http://www.example.com(协议不同) - ❌
https://api.example.com(域名不同) - ❌
https://www.example.com:8080(端口不同)
限制范围
AJAX/Fetch请求(核心限制)Cookie/LocalStorage访问DOM操作(如iframe内页面)Web Workers脚本加载
跨域解决方案
CORS跨域资源共享 -> 主流方案
服务端通过设置
HTTP Header声明允许的跨域来源。
- 简单请求
Simple Request
条件
GET/HEAD/POST方法,且Content-Type为text/plain、multipart/form-data、application/x-www-form-urlencoded。
流程
GET /data HTTP/1.1
Host: api.example.com
Origin: https://www.example.com // 浏览器自动添加
服务器响应
HTTP/1.1 200 OK
Access-Control-Allow-Origin // 必须指定明确域名或*
Access-Control-Allow-Credentials: true // 允许携带Cookie
- 预检请求
Preflight Request
非简单请求(如PUT、Content-Type: application/json)会先发OPTIONS请求
OPTIONS /data HTTP/1.1
Access-Control-Request-Method: PUT
Access-Control-Request-Header:X-Custom-Header
Origin: https://www.example.com
服务器响应
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 86400 // 缓存预检结果(秒)
JSONPJson With Padding -> 历史方案
- 利用
<scrpit>标签无跨域限制的特性
// 前端
const handleResponse = (data) => {
// ...
}
const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
// 服务端返回
handleResponse({ name:'xx' });
- 缺点 仅支持
GET请求,错误处理困难,存在XSS风险。
WebSocket
- WebSocket协议本身支持跨域
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = (event) => {
console.log(JSON.parse(event.data));
};
代理服务器Proxy
- 通过同源服务器中转请求。
- 开发环境代理 -> 如
webpack-dev-server
// webpack.config.js
devServer: {
proxy: {
'/api': 'http://localhost:3000' // 将 /api 请求代理到后端
}
}
- 生产环境
Nginx配置
server {
location /api {
proxy_pass http://backend-server:3000;
add_header Access-Control-Allow-Origin *;
}
}
postMessage
- 跨窗口通信 如
iframe与父页面
// 父页面向iframe发送消息
const iframe = document.getElementById('my-iframe');
iframe.contentWindow.postMessage('Hello!', 'https://child.example.com');
// iframe接收消息
window.addEventListener('message', (event) => {
if (event.origin !== 'https://parent.example.com') return;
// ...
});
修改document.domain -> 仅限子域
- 适用于同一主域下的跨子域场景(如
a.example.com<->b.example.com)
// 双方页面设置
document.domain = 'example.com';