【前端知识点总结】前端跨域问题

在前端开发中,"跨域"是一个绕不开的话题。无论是调用第三方 API,还是前后端分离项目中的本地联调,我们都可能遇到它。

一、什么是跨域问题?

要理解跨域,首先要知道什么是同源策略

同源策略(Same-Origin Policy)是浏览器最核心也是最基本的安全功能。它规定了一个源(origin)的文档或脚本,不能读取或修改另一个源的文档属性。

所谓同源 ,指的是协议、域名、端口号三者完全相同。

举个例子:

|----------------------------|--------------------------------------|----------|-------------------------------------------|
| URL A | URL B | 是否同源 | 原因 |
| http://www.example.com/ | http://www.example.com/dir/page.html | 是 | 协议、域名、端口均相同 |
| http://www.example.com/ | https://www.example.com/ | 否 | 协议不同 (http vs https) |
| http://www.example.com/ | http://api.example.com/ | 否 | 域名不同 (www.example.com vs api.example.com) |
| http://www.example.com:80/ | http://www.example.com:8080/ | 否 | 端口不同 (80 vs 8080) |

跨域(Cross-Origin) 就是指一个源的文档或脚本试图请求另一个源的资源。当浏览器发现这是一个跨域请求,并且该请求不符合某些安全例外(如 CORS),浏览器就会出于安全考虑,阻止该请求或限制对响应的访问,这就是我们常说的跨域问题。

注:跨域问题本质上是浏览器的行为。服务器之间(如后端服务 A 调用后端服务 B)的 HTTP 请求不存在跨域问题。

二、前端为什么会有跨域问题?

同源策略的存在主要是为了保护用户信息安全,防止恶意网站窃取数据。想象一下,如果没有同源策略:

  1. 你登录了网上银行 https://mybank.com,浏览器保存了你的登录凭证(Cookie)。
  2. 你在不经意间访问了一个恶意网站 https://evil.com
  3. 这个恶意网站的页面里有一段 JavaScript 代码,向 https://mybank.com/api/transfer?to=hacker\&amount=10000 发起了请求。
  4. 由于没有同源策略,浏览器会自动附上 mybank.com 的 Cookie,服务器会验证通过,执行转账操作。
  5. 你的钱就这样被悄无声息地转走了。

同源策略就是为了防止这种情况发生。它限制了 evil.com 的脚本读取 mybank.com 返回的响应数据,从而保护了用户信息。

三、如何解决跨域问题?

既然跨域是浏览器的一种安全限制,那么解决方案也必然围绕如何"告诉"浏览器这个跨域请求是安全的,或者如何绕过这个限制。以下是几种主流的解决方案:

1. CORS (Cross-Origin Resource Sharing) - 跨域资源共享

这是目前最推荐、最规范的解决方案。它是一种 HTTP 机制,允许服务器标示除了它自己以外的其他 origin(域、协议或端口),这样浏览器就可以访问加载这些资源。

CORS 的工作原理是:当浏览器发起一个跨域请求时,它会自动在请求头中添加一些信息(如 `Origin`),服务器根据这些信息判断是否允许该跨域请求,并在响应头中返回相应的许可信息。浏览器收到响应后,如果检查到服务器允许该请求,就不会报错。

简单请求预检请求

CORS 将请求分为两类:

  • 简单请求:满足一定条件(如方法是 GET/POST/HEAD,Content-Type 为 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)的请求。浏览器会直接发送请求,并在响应头中检查 Access-Control-Allow-Origin。
  • 非简单请求:如使用 PUT、DELETE 方法,或 Content-Type 为 application/json 的请求。浏览器会先发送一个 OPTIONS 方法的"预检请求"(Preflight Request)到服务器,询问是否允许该跨域请求。服务器确认允许后,浏览器才会发送真正的请求。

服务端配置示例 (Node.js + Express):

javascript 复制代码
const express = require('express');
const app = express();
const port = 3001; // 后端服务端口

// 允许所有源跨域 (仅用于开发,生产环境应指定具体域名)
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*'); // 或 'http://localhost:3000'
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
  // 如果需要携带 Cookie
  // res.header('Access-Control-Allow-Credentials', 'true');
  
  // 处理预检请求
  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    next();
  }
});

app.get('/api/data', (req, res) => {
  res.json({ message: '这是来自跨域服务器的数据!' });
});

app.listen(port, () => {
  console.log(`后端服务运行在 http://localhost:${port}`);
});

前端调用示例 (使用 Fetch API):

javascript 复制代码
// 假设前端运行在 http://localhost:3000
fetch('http://localhost:3001/api/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

企业开发实践:

在生产环境中,Access-Control-Allow-Origin 不应设置为 *,而应设置为前端应用的域名,如 https://www.myapp.com,以增强安全性。通常,这些 CORS 头会在反向代理(如 Nginx)或 API 网关层面统一配置。

2. 代理服务器

代理服务器是解决跨域问题的"万能钥匙"。其核心思想是:浏览器有跨域限制,但服务器之间没有。

原理:

  1. 前端应用向同源的代理服务器发送请求。
  2. 代理服务器接收到请求后,将其转发给真正的目标后端服务器(跨域)。
  3. 后端服务器将响应返回给代理服务器。
  4. 代理服务器再将响应返回给前端应用。

对于前端来说,它始终是在和同源的代理服务器通信,因此不存在跨域问题。

企业开发实践:

开发环境: 前端构建工具(如 Vite, Webpack)通常内置了代理功能。Vite ( vite.config.js ):

javascript 复制代码
import { defineConfig } from 'vite';
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3001', // 真实后端服务地址
        changeOrigin: true, // 修改请求头中的 Origin 为目标地址
        // 可选:重写路径
        // rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

注意:前端代码中请求 /api/data,Vite 开发服务器会自动将其代理到 http://localhost:3001/api/data。

Webpack (vue.config.js 或 webpack.config.js):

javascript 复制代码
module.exports = {
 devServer: {
   proxy: {
     '/api': {
       target: 'http://localhost:3001',
       changeOrigin: true,
       pathRewrite: { '^/api': '' } // 重写路径,移除 /api
     }
   }
 }
};

生产环境: 通常使用 Nginx 作为反向代理。

Nginx 配置示例 (nginx.conf):

javascript 复制代码
server {
   listen 80;
   server_name www.myapp.com;
   location / {
       root /usr/share/nginx/html;
       index index.html;
       try_files $uri $uri/ /index.html; # SPA 路由支持
   }

   location /api/ {
       proxy_pass http://backend-server:3001/; # 后端服务地址
       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;
  }
}

这样,前端访问 www.myapp.com/api/data,Nginx 会将其代理到 http://backend-server:3001/data。

3. JSONP (JSON with Padding) - 仅支持 GET 请求

JSONP 是一种比较"古老"的跨域解决方案,它利用了 <script> 标签不受同源策略限制的特性。

原理:

  1. 前端定义一个回调函数,如 handleResponse 。
  2. 创建一个 <script> 标签,其 src 指向跨域 API,并将回调函数名作为参数传递,如 http://api.example.com/data?callback=handleResponse
  3. 服务器接收到请求后,将数据包裹在回调函数中返回,如 handleResponse({"name": "Alice", "age": 25}); 。
  4. 浏览器接收到并执行这段 JavaScript,从而调用前端定义的回调函数,并将数据作为参数传入。

服务端示例 (Node.js + Express):

javascript 复制代码
const express = require('express');
const app = express();
const port = 3002;

app.get('/api/jsonp', (req, res) => {
  const callbackName = req.query.callback;
  const data = { message: '这是 JSONP 返回的数据!' };
  const script = `${callbackName}(${JSON.stringify(data)})`;
  res.send(script);
});

app.listen(port, () => {
  console.log(`JSONP 服务运行在 http://localhost:${port}`);
});

前端调用示例:

javascript 复制代码
function handleResponse(data) {
  console.log('JSONP 响应:', data);
}

function loadJSONP() {
  const script = document.createElement('script');
  script.src = 'http://localhost:3002/api/jsonp?callback=handleResponse';
  document.body.appendChild(script);
  
  // 可选:请求完成后移除 script 标签
  script.onload = () => {
    document.body.removeChild(script);
  };
}

loadJSONP();

缺点:

  • 只支持 GET 请求。
  • 安全性较低,容易受到 XSS 攻击(如果服务器对回调函数名过滤不严)。
  • 错误处理困难。

现在,CORS 已经普及,JSONP 逐渐被淘汰,但在与一些只支持 JSONP 的老旧第三方服务交互时,可能还会用到。

4. 其他方案(了解即可)

  • WebSocket: WebSocket 协议不受同源策略限制,可以进行跨域通信。
  • postMessage: 用于不同窗口(iframe、popup)之间的安全跨域通信。
  • document.domain: 只适用于主域相同、子域不同的情况(如 a.example.comb.example.com ),现在已不推荐使用。

四、方案的对比与选择

  1. 跨域是浏览器安全策略(同源策略)导致的问题,目的是保护用户数据。
  2. CORS 是现代 Web 开发解决跨域问题的标准方案,需要后端服务器设置特定的 HTTP 响应头。
  3. 代理服务器是一种非常实用的绕过方案,尤其在开发环境和需要统一 API 网关的生产环境中。
  4. JSONP 是一种过时的技术,仅在特定兼容性需求下考虑。
相关推荐
尽欢i3 小时前
踩过坑才懂:前端生成唯一 ID,别用 Date.now ()了!一行代码搞定
前端·javascript
JS_GGbond3 小时前
解锁 JavaScript 对象的“魔法宝箱”:这些方法让你玩转对象操作
前端·javascript
是杉杉吖~3 小时前
《5 分钟上手 React Flex 容器:从 0 搭建响应式卡片列表》
前端·react.js·前端框架
大江东第一深情3 小时前
Origin 2024 进行语言切换后仍然显示为英文
运维·前端
lxh01133 小时前
最长公共子序列
前端·数据结构
Можно3 小时前
ES6扩展运算符:从基础到实战的全方位解析
前端·javascript
豆苗学前端3 小时前
闭包、现代JS架构的基石(吊打面试官)
前端·javascript·面试
雯0609~3 小时前
uni-app:防止重复提交
前端·javascript·uni-app
2501_918126913 小时前
用html5写一个国际象棋
前端·javascript·css