跨域

解决跨域问题有四种方案:jsonp(需要前后端协作,不安全),打包工具(纯前端,只能在开发阶段解决跨域),设置相应头(纯后端),nginx

1. 什么是跨域?

跨域本质上是浏览器为了安全而引入的一种 同源策略 (Same-Origin Policy)。

当一个请求 URL 的 协议 (Protocol)、域名 (Domain)、端口 (Port) 三者中,只要有一个与当前页面的 URL 不同,就会产生跨域。

1.1 同源策略判断表

假设当前页面地址为: http://www.example.com/index.html

请求地址结果原因
http://www.example.com/api/data成功同源
https://www.example.com/api/data跨域协议不同 (http vs https)
http://api.example.com/data跨域域名不同 (www vs api)
http://www.example.com:8080/data跨域端口不同 (80 vs 8080)
http://www.google.com/api跨域域名完全不同

1.2 为什么要有同源策略?

主要是为了安全。如果没有同源策略,恶意网站可能会窃取你在其他网站(如银行)的敏感数据(Cookies、LocalStorage),或者利用你的登录状态伪造请求(CSRF 攻击)。

2. 常见的解决方案

解决跨域的核心思路通常有两种:

  1. 后端配合:允许特定的源访问(CORS)。
  2. 绕过浏览器:通过服务器代理转发请求(因为服务器之间没有同源策略)。

以下是 5 种最常用的解决方案:

方案一:CORS (Cross-Origin Resource Sharing) 【最推荐】

这是目前最标准、最通用的解决方案。它需要后端在响应头中添加特定的字段,告诉浏览器:“我允许这个源访问我”。

核心响应头:

  • Access-Control-Allow-Origin: 允许访问的源(例如 http://localhost:3000 或 *)。
  • Access-Control-Allow-Methods: 允许的请求方法(GET, POST, PUT, DELETE 等)。
  • Access-Control-Allow-Headers: 允许的自定义 Header。

代码示例 (Node.js/Express):

text
123456789101112131415161718192021
const express = require('express');
const app = express();

// 方法 1: 手动设置 Header
app.use((req, res, next) => {
    // 允许所有来源访问,生产环境建议指定具体域名
    res.header('Access-Control-Allow-Origin', '*'); 
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
    next();
});

// 方法 2: 使用 cors 中间件 (推荐)
const cors = require('cors');
app.use(cors()); 

app.get('/api/data', (req, res) => {
    res.json({ msg: '这是跨域数据' });
});

app.listen(3000);

方案二:开发环境代理 (Proxy) 【前端开发常用】

在前端开发阶段(使用 Vue, React, Webpack, Vite 等),由于本地服务器(如 localhost:3000)和后端接口(如 https://www.google.com/search?q=api.server.com)不同源,常使用本地代理解决。

原理: 浏览器 -> 本地开发服务器 (同源) -> 转发请求到后端服务器。 服务器与服务器之间通信不受同源策略限制。

代码示例 (Vite vite.config.js):

text
123456789101112
export default defineConfig({
  server: {
    proxy: {
      // 当请求路径以 /api 开头时
      '/api': {
        target: '[http://backend-api.com](http://backend-api.com)', // 目标后端地址
        changeOrigin: true, // 修改请求头的 Host 为目标地址
        rewrite: (path) => path.replace(/^\/api/, '') // 去掉 /api 前缀
      }
    }
  }
})

注意:这只在开发环境有效,生产环境通常需要 Nginx 配合。

方案三:Nginx 反向代理 【生产环境常用】

在项目上线时,通常使用 Nginx 作为反向代理服务器。前端请求 Nginx,Nginx 再转发给后端,从而实现“同源”的效果。

Nginx 配置示例 (nginx.conf):

text
12345678910111213141516171819
server {
    listen 80;
    server_name [www.example.com](https://www.example.com);

    # 前端静态资源
    location / {
        root /usr/share/nginx/html;
        index index.html;
    }

    # 接口代理
    location /api/ {
        proxy_pass http://backend-server:8080/; # 转发到真实后端
        
        # 也可以在这里配置 CORS 头
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    }
}

方案四:JSONP (JSON with Padding) 【老旧方案】

JSONP 是利用 <script> 标签不受同源策略限制的特性来实现跨域的。

  • 缺点:只支持 GET 请求,不安全(容易受 XSS 攻击),现在已经很少使用,主要用于兼容极老的系统。

前端实现原理:

text
1234567
<script>
    function handleResponse(data) {
        console.log('获取到的数据:', data);
    }
</script>
<!-- 请求后端,并告诉后端回调函数名是 handleResponse -->
<script src="[http://api.example.com/data?callback=handleResponse](http://api.example.com/data?callback=handleResponse)"></script>

方案五:Websocket

Websocket 协议本身不受同源策略限制。如果你使用 Socket.io 等库进行通信,天然支持跨域。

3. 总结与建议

场景推荐方案备注
日常开发调试Proxy (代理)配置简单,无侵入,仅对开发环境有效
生产环境 APICORS标准做法,后端控制权限,支持所有 HTTP 方法
项目部署Nginx 反向代理性能高,架构清晰,隐藏真实后端地址
老旧系统兼容JSONP仅限 GET 请求,不推荐新项目使用

最佳实践流程:

  1. 开发时:前端配置 proxy 解决本地开发跨域。
  2. 上线时:后端配置 CORS 允许特定的线上域名访问,或者运维配置 Nginx 反向代理将前端和后端聚合在同一个域下。