跨域

59 阅读2分钟

同源策略

定义

当两个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-Typetext/plainmultipart/form-dataapplication/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
    非简单请求(如PUTContent-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';

方案对比