跨域请求的详细实现方法解析

跨域问题是前端开发中的常见挑战,由于浏览器的同源策略 (Same-Origin Policy) 限制,不同源 (协议、域名、端口任一不同) 的请求会被阻止。以下是全面的跨域解决方案:

1. CORS (跨域资源共享)

最主流的跨域解决方案,需要服务器端配合设置响应头。

简单请求实现

// 服务器设置响应头
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有域
  // 或指定特定域
  // res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
  next();
});

预检请求 (Preflight) 实现

// 服务器设置
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true'); // 允许携带凭证
  res.setHeader('Access-Control-Max-Age', '86400'); // 预检结果缓存时间
  
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  next();
});

客户端代码

fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  credentials: 'include' // 携带cookie等凭证
})
.then(response => response.json())
.then(data => console.log(data));

2. JSONP (仅限 GET 请求)

适用于旧浏览器或不支持 CORS 的环境,但只支持 GET 请求。

实现方式

function jsonp(url, callbackName, callback) {
  const script = document.createElement('script');
  script.src = `${url}?callback=${callbackName}`;
  
  window[callbackName] = function(data) {
    callback(data);
    document.body.removeChild(script);
    delete window[callbackName];
  };
  
  document.body.appendChild(script);
}
 
// 使用
jsonp('https://api.example.com/data', 'handleData', (data) => {
  console.log('Received:', data);
});

服务器端实现

app.get('/data', (req, res) => {
  const callbackName = req.query.callback;
  const data = { message: 'Hello from JSONP' };
  res.send(`${callbackName}(${JSON.stringify(data)})`);
});

3. 代理服务器

前端开发中最常用的本地开发解决方案

3.1 Webpack DevServer 代理

// webpack.config.js
module.exports = {
  // ...
  devServer: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
};

3.2 Nginx 反向代理

server {
    listen 80;
    server_name local.example.com;
    
    location /api/ {
        proxy_pass https://api.example.com/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

3.3 Node.js 中间件代理

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
 
const app = express();
 
app.use('/api', createProxyMiddleware({ 
  target: 'https://api.example.com',
  changeOrigin: true,
  pathRewrite: { '^/api': '' }
}));
 
app.listen(3000);

4. WebSocket

WebSocket 协议不受同源策略限制

const socket = new WebSocket('wss://api.example.com/socket');
 
socket.onopen = function() {
  socket.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
};
 
socket.onmessage = function(event) {
  console.log('Message from server:', event.data);
};

5. postMessage (跨文档通信)

适用于 iframe/window 之间的通信

父窗口代码

const iframe = document.getElementById('my-iframe');
iframe.contentWindow.postMessage({ key: 'value' }, 'https://child.example.com');
 
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://child.example.com') return;
  console.log('Received:', event.data);
});

子窗口代码

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://parent.example.com') return;
  console.log('Received:', event.data);
  event.source.postMessage('Response data', event.origin);
});

6. document.domain (仅限同主域)

适用于主域相同、子域不同的情况

// 在a.example.com和b.example.com的页面中都设置
document.domain = 'example.com';

7. 浏览器扩展跨域

Chrome 扩展可以使用跨域 API

// manifest.json
{
  "permissions": ["https://api.example.com/"]
}

8. 图像 Ping

仅用于单向简单通信

function imagePing(url) {
  const img = new Image();
  img.src = url;
}
 
imagePing('https://api.example.com/track?data=info');

安全注意事项

  1. CORS 配置要精确,避免使用过于宽松的 Access-Control-Allow-Origin: *
  2. JSONP 存在 XSS 风险,确保信任数据源
  3. 代理服务器要验证和清理输入
  4. postMessage 要始终验证 event.origin
  5. 敏感操作应结合 CSRF 保护机制

方案选择建议

  1. 现代项目首选 CORS - 最标准、安全、灵活
  2. 开发环境用代理 - Webpack 或 Nginx 代理解决开发跨域
  3. 特殊场景考虑其他方案
    • 旧浏览器支持:JSONP
    • 实时通信:WebSocket
    • 跨窗口通信:postMessage
    • 同主域不同子域:document.domain

每种方案都有其适用场景,应根据项目需求、浏览器兼容性和安全要求选择合适的跨域解决方案。