网站建设中实现跨域请求的技术与方法 分类:公司动态 发布时间:2025-11-19

网站建设中,前后端分离架构已成为主流趋势 —— 前端页面部署在一个域名下,后端接口部署在另一个域名下,这种架构能显著提升开发效率与系统扩展性,但也必然面临 “跨域请求” 的技术难题。当浏览器检测到前端脚本向非同源域名发送请求时,会基于 “同源策略” 拦截响应,导致数据交互失败。本文将从跨域请求的技术原理入手,详细拆解 6 种主流实现技术的核心逻辑、操作步骤、优缺点及适用场景,为网站开发者提供可落地的跨域解决方案。
 
一、跨域请求的技术背景:为什么会被拦截?
 
在深入技术方案前,需先明确 “跨域拦截的本质”—— 并非请求未发送,而是浏览器收到响应后,发现响应头中缺少 “允许跨域” 的合法标识,才主动拦截数据,不让前端脚本获取。
 
1. 同源策略的核心限制
浏览器的同源策略(Same-Origin Policy)规定,只有当 “协议、域名、端口” 三者完全一致时,才视为 “同源”,允许无限制数据交互;若任意一项不同,即属于 “跨域”,会触发以下限制:
a. 无法读取非同源网站的 Cookie、LocalStorage、SessionStorage;
b. 无法操作非同源网站的 DOM 元素;
c. 无法接收非同源 AJAX/fetch 请求的响应数据(请求能发送,但响应会被拦截)。
例如:前端部署在 http://www.frontend.com:80,向后端 https://api.backend.com:443 发送请求,因 “协议(http/https)、域名、端口” 均不同,属于跨域请求,浏览器会拦截响应。
 
2. 跨域请求的 “预请求” 机制
对于 “非简单请求”(如请求方法为 PUT/DELETE、请求头含自定义字段、发送 JSON 格式数据),浏览器会先发送一次 “预请求”(OPTIONS 请求),询问后端:“是否允许当前域名的这个请求?”。只有当后端明确返回 “允许” 的响应头后,浏览器才会发送真正的业务请求;若预请求失败,业务请求会直接被阻断。
理解这一机制是选择跨域方案的关键 —— 所有跨域技术的核心,本质都是 “让后端或中间层向浏览器返回合法的跨域响应头”,或 “绕开浏览器的同源策略检测”。
 
二、6 种主流跨域技术:原理、操作与场景
 
1. 后端配置 CORS:最标准、最推荐的方案
CORS(Cross-Origin Resource Sharing,跨域资源共享)是 W3C 制定的官方跨域标准,通过后端在响应头中添加 “允许跨域” 的标识,直接告知浏览器 “该响应可被前端获取”,无需前端额外修改代码,是目前前后端分离架构的首选方案。
 
(1)核心原理
后端在响应头中添加以下关键字段,浏览器检测到这些字段后,会允许前端脚本读取响应数据:
a. Access-Control-Allow-Origin:指定允许跨域的域名(如 http://www.frontend.com,或用 * 允许所有域名,但不支持带 Cookie 的请求);
b. Access-Control-Allow-Methods:指定允许的请求方法(如 GET, POST, PUT, DELETE);
c. Access-Control-Allow-Headers:指定允许的自定义请求头(如 Content-Type, Token);
d. Access-Control-Allow-Credentials:值为 true 时,允许跨域请求携带 Cookie(此时 Access-Control-Allow-Origin 不能设为 *,需指定具体域名);
e. Access-Control-Max-Age:指定预请求(OPTIONS)的缓存时间(如 86400,表示 24 小时内无需重复发送预请求)。
 
(2)操作步骤(以常见后端语言为例)
 
① Node.js(Express 框架)
通过中间件 cors 快速配置,无需手动写响应头:
 
// 1. 安装依赖
npm install cors
 
// 2. 引入并配置
const express = require('express');
const cors = require('cors');
const app = express();
 
// 基础配置:允许所有域名(不支持带 Cookie)
app.use(cors());
 
// 进阶配置:允许指定域名 + 支持 Cookie
app.use(cors({
  origin: 'http://www.frontend.com', // 前端域名
  credentials: true, // 允许携带 Cookie
  methods: ['GET', 'POST', 'PUT', 'DELETE'], // 允许的方法
  allowedHeaders: ['Content-Type', 'Token'] // 允许的自定义头
}));
 
// 后端接口
app.get('/api/data', (req, res) => {
  res.send({ code: 200, data: '跨域请求成功' });
});
 
app.listen(3000);
 
② Java(Spring Boot 框架)
通过 @CrossOrigin 注解或全局配置类实现:
 
// 方式1:单个接口配置
@RestController
@RequestMapping("/api")
public class DataController {
  // 允许指定前端域名跨域
  @CrossOrigin(origins = "http://www.frontend.com", allowCredentials = "true")
  @GetMapping("/data")
  public Map<String, Object> getData() {
    Map<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("data", "跨域请求成功");
    return result;
  }
}
 
// 方式2:全局配置(所有接口生效)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/api/**") // 对 /api 下所有接口生效
            .allowedOrigins("http://www.frontend.com") // 允许的前端域名
            .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的方法
            .allowedHeaders("Content-Type", "Token") // 允许的自定义头
            .allowCredentials(true) // 允许携带 Cookie
            .maxAge(86400); // 预请求缓存 24 小时
  }
}
 
③ PHP
直接在响应中添加头信息:
 
<?php
// 允许指定前端域名跨域
header("Access-Control-Allow-Origin: http://www.frontend.com");
// 允许携带 Cookie
header("Access-Control-Allow-Credentials: true");
// 允许的请求方法
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
// 允许的自定义头
header("Access-Control-Allow-Headers: Content-Type, Token");
// 预请求缓存时间
header("Access-Control-Max-Age: 86400");
 
// 处理请求并返回数据
$response = array("code" => 200, "data" => "跨域请求成功");
echo json_encode($response);
?>
 
(3)优缺点与适用场景
优点:官方标准,兼容性好(支持所有现代浏览器),配置简单,无需前端额外开发,支持所有请求方法与数据格式;
缺点:需后端配合配置,若后端是第三方接口(无法修改配置),则无法使用;
适用场景:前后端均由自己开发的项目(如公司内部系统、自有产品),是首选方案。
 
2. 前端使用 JSONP:兼容老浏览器的 “临时方案”
JSONP是早期跨域方案,利用 “<script> 标签不受同源策略限制” 的特性 —— 通过动态创建 <script> 标签,向目标域名发送请求,后端将数据包装成 “函数调用” 的格式返回,前端提前定义好该函数,即可获取数据。
 
(1)核心原理
a. 前端定义一个处理数据的回调函数(如 handleJsonpData);
b. 动态创建 <script> 标签,src 指向后端接口,同时通过 URL 参数传递回调函数名(如 http://api.backend.com/data?callback=handleJsonpData);
c. 后端接收请求后,将数据包装成 handleJsonpData({code:200, data:"..."}) 的格式返回;
d. 浏览器加载 <script> 标签时,会执行返回的函数调用,前端通过回调函数获取数据。
 
(2)操作步骤
 
① 前端实现(原生 JS)
 
// 1. 定义回调函数
function handleJsonpData(response) {
  console.log("跨域数据获取成功:", response); // 输出 {code:200, data:"JSONP 跨域成功"}
}
 
// 2. 动态创建 script 标签
function requestJsonp() {
  const script = document.createElement('script');
  // 后端接口 + 传递回调函数名
  script.src = 'http://api.backend.com/data?callback=handleJsonpData';
  // 将 script 标签插入页面(发送请求)
  document.body.appendChild(script);
  // 请求完成后移除 script 标签(避免冗余)
  script.onload = function() {
    document.body.removeChild(script);
  };
}
 
// 调用函数发送请求
requestJsonp();
 
② 后端实现(Node.js Express)
 
const express = require('express');
const app = express();
 
app.get('/data', (req, res) => {
  // 1. 获取前端传递的回调函数名
  const callbackName = req.query.callback;
  // 2. 准备要返回的数据
  const data = { code: 200, data: "JSONP 跨域成功" };
  // 3. 将数据包装成“函数调用”格式返回
  const response = `${callbackName}(${JSON.stringify(data)})`;
  res.send(response); // 返回结果:handleJsonpData({"code":200,"data":"JSONP 跨域成功"})
});
 
app.listen(3000);
 
(3)优缺点与适用场景
优点:兼容性极强(支持 IE6/7 等老浏览器),实现简单;
缺点:仅支持 GET 请求(因 <script> 标签只能发送 GET 请求),无错误处理机制(请求失败无法捕获),存在 XSS 安全风险(后端若返回恶意代码,会直接执行);
适用场景:需兼容老浏览器的 legacy 项目,或对接仅支持 JSONP 的第三方接口(如早期的天气、地图 API),现代项目不推荐使用。
 
3. 后端配置反向代理:隐藏跨域的 “中间层方案”
反向代理是通过 “中间服务器” 转发请求 —— 前端不直接向后端跨域接口发送请求,而是向 “同源的代理服务器” 发送请求,代理服务器再向后端跨域接口转发请求,最后将后端响应返回给前端。由于前端与代理服务器同源,浏览器不会触发跨域拦截。
 
(1)核心原理
a. 前端(http://www.frontend.com)向同源的代理服务器(如 http://www.frontend.com/proxy)发送请求;
b. 代理服务器(如 Nginx、Node.js 中间件)接收请求后,向后端跨域接口(http://api.backend.com/data)转发请求;
c. 后端将响应返回给代理服务器,代理服务器再将响应转发给前端;
d. 因前端与代理服务器同源,浏览器不会拦截响应,实现 “隐藏跨域”。
 
(2)主流代理实现:Nginx 配置(最常用)
Nginx 是高性能的 HTTP 服务器,常作为反向代理服务器使用,配置简单且性能优异,适合生产环境。
 
① Nginx 核心配置(nginx.conf)
 
http {
  server {
    listen 80;
    server_name www.frontend.com; # 前端域名(与前端同源)
 
    # 配置反向代理:前端请求 /api 路径时,转发到后端接口
    location /api/ {
      # 后端跨域接口的真实地址(注意:结尾的 / 需与 location 对应)
      proxy_pass http://api.backend.com/;
      
      # 关键配置:传递真实的请求头(避免后端获取不到正确的域名、IP 等信息)
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }
 
    # 配置前端静态资源(如 HTML、CSS、JS)
    location / {
      root /usr/local/nginx/html/frontend; # 前端文件存放路径
      index index.html;
    }
  }
}
 
② 前端请求方式(无需修改代码)
前端只需向 “同源的 /api 路径” 发送请求,无需关注代理逻辑:
 
// 前端请求:看似向同源路径发送请求,实际由 Nginx 转发到后端
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log("跨域数据:", data)) // 正常获取后端响应
  .catch(error => console.error("请求错误:", error));
 
(3)其他代理方式:开发环境临时方案
a. Vue CLI 代理:在 vue.config.js 中配置,适合 Vue 项目开发阶段:
 
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://api.backend.com', // 后端接口地址
        changeOrigin: true, // 开启代理(模拟同源请求)
        pathRewrite: { '^/api': '' } // 移除请求路径中的 /api(若后端接口无 /api 前缀)
      }
    }
  }
};
 
b. Webpack 代理:在 webpack.config.jsdevServer.proxy 中配置,原理与 Vue CLI 一致。
 
(4)优缺点与适用场景
优点:前端无需任何修改(完全感知不到跨域),兼容性好(支持所有请求方法与数据格式),可隐藏后端真实地址(提升安全性),适合生产环境;
缺点:需额外部署代理服务器(如 Nginx),增加运维成本;
适用场景:生产环境项目(尤其是前后端域名不同,但可控制中间代理层的场景),或开发阶段需要临时绕开跨域的场景。
 
4. 使用 WebSocket:全双工通信的跨域方案
WebSocket 是 HTML5 新增的全双工通信协议,通过 “一次握手” 建立持久连接,允许服务器主动向前端推送数据。由于 WebSocket 协议本身不受同源策略限制,可直接实现跨域通信,适合实时通信场景(如聊天、弹幕、实时数据监控)。
 
(1)核心原理
a. 前端通过 new WebSocket('ws://api.backend.com/ws') 向后端发起 WebSocket 连接请求;
b. 后端接收连接请求后,完成 “握手”(HTTP 协议升级为 WebSocket 协议);
c. 连接建立后,前端与后端可双向发送数据(如 JSON 格式),无需每次请求都携带跨域头,且不受同源策略限制。
 
(2)操作步骤
 
① 后端实现(Node.js + ws 库)
 
// 1. 安装依赖
npm install ws
 
// 2. 创建 WebSocket 服务器
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 }); // 后端 WebSocket 端口
 
// 监听连接事件
wss.on('connection', (ws) => {
  console.log("客户端已连接");
 
  // 接收前端发送的数据
  ws.on('message', (message) => {
    console.log("收到前端数据:", message.toString());
    // 向后端发送数据(跨域无限制)
    ws.send(JSON.stringify({ code: 200, data: "WebSocket 跨域通信成功" }));
  });
 
  // 监听连接关闭事件
  ws.on('close', () => {
    console.log("客户端已断开连接");
  });
});
 
② 前端实现(原生 JS)
 
// 1. 建立 WebSocket 连接(注意协议:ws 对应 http,wss 对应 https)
const ws = new WebSocket('ws://api.backend.com:3000');
 
// 2. 连接成功事件
ws.onopen = () => {
  console.log("WebSocket 连接已建立");
  // 向前端发送数据(跨域无限制)
  ws.send(JSON.stringify({ action: "get_data", params: { id: 1 } }));
};
 
// 3. 接收后端发送的数据
ws.onmessage = (event) => {
  const response = JSON.parse(event.data);
  console.log("收到后端数据:", response); // 输出 {code:200, data:"WebSocket 跨域通信成功"}
};
 
// 4. 连接关闭事件
ws.onclose = () => {
  console.log("WebSocket 连接已关闭");
};
 
// 5. 连接错误事件
ws.onerror = (error) => {
  console.error("WebSocket 错误:", error);
};
 
(3)优缺点与适用场景
优点:全双工通信(服务器可主动推送数据),无跨域限制,连接持久(减少请求开销);
缺点:需后端支持 WebSocket 协议,不适合简单的 “一次性数据请求”(如列表查询),老浏览器(如 IE < 10)不支持;
适用场景:实时通信场景,如在线聊天、实时数据监控、弹幕系统、协同编辑工具等。
 
5. 后端设置 document.domain:仅适合同主域名的跨域
若两个域名 “主域名相同,子域名不同”(如 a.frontend.com 和 b.frontend.com),可通过设置 document.domain 让浏览器认为它们是 “同源”,从而实现跨域通信(如共享 Cookie、操作 DOM)。
 
(1)核心原理
document.domain 是浏览器提供的属性,默认值为当前域名(如 a.frontend.com)。若两个页面都将 document.domain 设置为 “主域名”(如 frontend.com),浏览器会忽略子域名差异,视为同源,允许数据交互。
 
(2)操作步骤
 
① 页面 A(a.frontend.com/pageA.html)
 
<!DOCTYPE html>
<html>
<body>
  <script>
    // 设置 document.domain 为主域名
    document.domain = 'frontend.com';
    // 创建 iframe,加载页面 B(子域名不同)
    const iframe = document.createElement('iframe');
    iframe.src = 'http://b.frontend.com/pageB.html';
    iframe.onload = function() {
      // 访问 iframe 中的页面 B 的数据(跨域成功)
      console.log("页面 B 的数据:", iframe.contentWindow.pageBData); // 输出 "我是页面 B 的数据"
    };
    document.body.appendChild(iframe);
  </script>
</body>
</html>
 
② 页面 B(b.frontend.com/pageB.html)
 
<!DOCTYPE html>
<html>
<body>
  <script>
    // 必须与页面 A 设置相同的主域名
    document.domain = 'frontend.com';
    // 定义页面 B 的数据,供页面 A 访问
    window.pageBData = "我是页面 B 的数据";
  </script>
</body>
</html>
 
(3)优缺点与适用场景
优点:实现简单,适合共享 Cookie 或操作同主域名的 iframe 内容;
缺点:仅适合同主域名的子域名跨域(如 a.xxx.com 和 b.xxx.com),无法用于完全不同的域名(如 xxx.com 和 yyy.com),且现代浏览器对 document.domain 的设置有严格限制(如不能设置为非主域名);
适用场景:老项目中同主域名的子域名跨域需求,如公司内部不同业务线的子域名页面交互。
 
6. 使用 postMessage:跨窗口 / 跨域名的通用通信
postMessage 是 HTML5 新增的 API,允许不同域名的页面(或窗口、iframe)之间安全地传递数据,无需后端配合,适合前端页面之间的跨域通信(如 iframe 与父页面、多个窗口之间)。
 
(1)核心原理
a. 发送方通过 targetWindow.postMessage(data, targetOrigin) 发送数据,其中 targetWindow 是目标窗口(如 iframe 的 contentWindowwindow.open 打开的窗口),targetOrigin 是目标域名(如 http://b.backend.com,或用 * 允许所有域名,但不安全);
b. 接收方通过监听 window message 事件,获取发送方传递的数据,并可验证发送方的域名(避免恶意数据)。
 
(2)操作步骤
 
① 父页面(http://a.frontend.com/parent.html)向 iframe 发送数据
 
<!DOCTYPE html>
<html>
<body>
  <!-- 加载跨域的 iframe 页面 -->
  <iframe id="crossIframe" src="http://b.backend.com/iframe.html"></iframe>
 
  <script>
    const iframe = document.getElementById('crossIframe');
 
    // 等待 iframe 加载完成(确保 iframe 页面已准备好接收数据)
    iframe.onload = function() {
      // 1. 向前端 iframe 发送数据
      // targetWindow: iframe.contentWindow(目标窗口)
      // data: 要发送的数据(可是字符串、对象等)
      // targetOrigin: 目标域名(精确到协议+域名+端口,确保安全)
      iframe.contentWindow.postMessage(
        { action: "send_data", content: "父页面的跨域数据" },
        "http://b.backend.com"
      );
    };
 
    // 2. 接收 iframe 发送的回调数据
    window.addEventListener('message', (event) => {
      // 验证发送方域名(避免恶意数据)
      if (event.origin !== "http://b.backend.com") return;
      // 获取 iframe 传递的数据
      console.log("收到 iframe 数据:", event.data); // 输出 {code:200, reply:"已收到父页面数据"}
    });
  </script>
</body>
</html>
 
② iframe 页面(http://b.backend.com/iframe.html)接收并回复数据
 
<!DOCTYPE html>
<html>
<body>
  <script>
    // 监听父页面发送的 message 事件
    window.addEventListener('message', (event) => {
      // 1. 验证发送方域名(关键!防止恶意网站发送数据)
      if (event.origin !== "http://a.frontend.com") return;
 
      // 2. 获取父页面传递的数据
      console.log("收到父页面数据:", event.data); // 输出 {action:"send_data", content:"父页面的跨域数据"}
 
      // 3. 向父页面回复数据
      // targetWindow: event.source(发送方窗口,即父页面)
      // data: 回复的数据
      // targetOrigin: 发送方域名(确保安全)
      event.source.postMessage(
        { code: 200, reply: "已收到父页面数据" },
        "http://a.frontend.com"
      );
    });
  </script>
</body>
</html>
 
(3)优缺点与适用场景
优点:无需后端配合,适合前端页面之间的跨域通信(iframe、多窗口),支持任意域名,安全性高(可验证发送方域名);
缺点:仅适用于前端页面之间的通信(无法用于前端向后端接口请求数据),需手动处理数据传递与验证;
适用场景:跨域名的 iframe 交互(如嵌入第三方支付页面、广告页面)、多窗口之间的通信(如 window.open 打开的跨域窗口)。
 
三、跨域技术选型指南:如何选择最合适的方案?
 
面对多种跨域方案,需根据 “项目架构、技术栈、场景需求” 综合选择,以下是具体选型建议:
 
1. 前后端分离项目(自有后端):推荐使用后端配置 CORS,它是官方标准,配置简单且支持所有请求方式;不推荐 JSONP、document.domain。
2. 生产环境(需隐藏后端地址):推荐采用反向代理(Nginx),其性能优异,可隐藏后端地址,提升安全性;不推荐 JSONP、postMessage。
3. 开发阶段临时跨域:推荐使用 Vue CLI/Webpack 代理,开发工具自带代理,无需额外部署服务器;不推荐 Nginx,因其配置复杂。
4. 实时通信(如聊天、监控):推荐使用 WebSocket,全双工通信可减少请求开销;不推荐 CORS,因其需频繁请求。
5. 同主域名的子域名跨域:推荐使用 document.domain,无需后端配合,实现简单;不推荐 CORS,因其配置繁琐。
6. 前端页面之间通信(iframe / 多窗口):推荐使用 postMessage,无需后端,支持任意域名,安全性高;不推荐 CORS,因其无法用于前端间通信。
7. 兼容 IE6/7 老浏览器:推荐使用 JSONP,它是老浏览器唯一支持的跨域方案;不推荐 WebSocket、postMessage。
8. 对接第三方接口(无法修改后端):推荐使用反向代理(Nginx),第三方接口无法修改,需通过代理转发;不推荐 CORS,因其无法配置。
 
四、跨域请求的安全注意事项
 
1. 避免过度开放权限:使用 CORS 时,Access-Control-Allow-Origin 尽量指定具体域名(而非 *),尤其是带 Cookie 的请求;
2. 验证发送方域名:使用 postMessage 时,必须通过 event.origin 验证发送方域名,防止恶意网站发送伪造数据;
3. 防范 XSS 攻击:JSONP 存在 XSS 风险,需确保后端返回的数据是安全的(无恶意脚本),或对数据进行转义;
4. 使用 HTTPS 协议:跨域请求若涉及敏感数据(如用户信息、支付数据),需使用 HTTPS 协议,防止数据被劫持;
5. 限制请求方法与头信息:CORS 中通过 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 限制允许的请求方法与头信息,避免不必要的权限开放。
 
跨域请求是网站建设中不可避免的技术难题,但通过合理选择方案,可高效解决。对于现代前后端分离项目,优先选择 “后端配置 CORS” 或 “反向代理(Nginx)”,这两种方案兼容性好、安全性高,且适合生产环境;对于特殊场景(如实时通信、前端页面交互),可选择 WebSocket 或 postMessage;JSONP 和 document.domain 仅推荐用于老项目或特殊子域名场景。
无论选择哪种方案,都需兼顾 “功能需求” 与 “安全性”,避免因过度开放权限导致安全风险。
在线咨询
服务项目
获取报价
意见反馈
返回顶部