基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

跨域是什么,如何解决?

知识点图片

解释了浏览器“同源策略”导致的跨域问题,并介绍了CORS(标准方案)和代理服务器两种主流解决方法。

这是一个关于“跨域”非常经典和重要的问题。我会用一个清晰、易于理解的方式来解释它,并提供详细的解决方案。


核心摘要

  • 是什么? 跨域(Cross-Origin)是指浏览器出于安全原因,限制一个网页去请求另一个不同“源”的资源。这个限制被称为“同源策略”(Same-Origin Policy)。
  • 为什么有这个限制? 为了保护你的信息安全。如果没有这个策略,你在A网站登录后,B网站的恶意脚本就能轻易地读取你在A网站的个人信息,造成严重的安全漏洞。
  • 怎么解决? 最主流、最标准的解决方案是 CORS(跨域资源共享)。其他方案包括 JSONP(已过时,不推荐)、代理服务器(非常实用)等。

第一部分:跨域是什么?

1. 理解“源”(Origin)

首先,我们需要理解什么是“源”。一个“源”由三个部分组成:

  1. 协议(Protocol):例如 httphttps
  2. 域名(Domain):例如 www.a.comapi.b.com
  3. 端口(Port):例如 :80:3000

只有这三者完全相同时,才属于“同源”。

举个例子:
假设我们的网页地址是 http://www.a.com/page.html

请求的URL 是否同源? 原因
http://www.a.com/other.html 协议、域名、端口都相同
https://www.a.com/page.html 协议不同 (http vs https)
http://api.a.com/data.json 域名不同 (子域名不同)
http://www.b.com/page.html 域名不同
http://www.a.com:8080/page.html 端口不同 (默认80 vs 8080)

2. 什么是“同源策略”(Same-Origin Policy)?

同源策略是浏览器的一个核心安全功能。它规定:

一个源的文档或脚本,不能与另一个源的资源进行交互。

这个策略主要限制了以下几种行为:

  • 无法读取或修改另一个源的 DOM。
  • 无法读取另一个源的 Cookie、LocalStorage 和 IndexedDB。
  • 无法发送 AJAX 请求到另一个源(这正是我们通常所说的“跨域问题”)。

需要注意的是:

  • 这个策略是由浏览器强制执行的。请求实际上已经发送到服务器了,但是浏览器在接收到响应后,会检查响应头。如果发现源不同且服务器没有明确许可,浏览器就会拦截这个响应,并在控制台报错。
  • <script>, <img>, <iframe>, <link> 这些标签不受同源策略限制,它们可以加载跨域资源。这也是 JSONP 方案能够存在的基础。

第二部分:如何解决跨域问题?

以下是几种主流的解决方案,按照推荐顺序排列。

方案一:CORS (Cross-Origin Resource Sharing) - 跨域资源共享【标准方案】

这是 W3C 标准,也是目前解决跨域问题最主流、最优雅的方法。

核心思想:
服务器通过在响应头(Response Header)中添加一些特殊的 Access-Control-* 字段,来告诉浏览器,我允许你这个源来访问我的资源。浏览器看到这些头信息后,就不会拦截响应。

实现方式:
修改服务器端代码,添加相应的响应头。

1. 简单请求 (Simple Request)
当请求满足以下所有条件时,它被视为“简单请求”:

  • 请求方法是 GETPOSTHEAD 之一。
  • HTTP 头信息不超出 Accept, Accept-Language, Content-Language, Content-Type 等几个字段。
  • Content-Type 的值仅限于 text/plain, multipart/form-data, application/x-www-form-urlencoded

对于简单请求,服务器只需要在响应头中添加一个字段:

plaintext
Access-Control-Allow-Origin: http://www.a.com

或者,允许任何源访问(不推荐在生产环境中使用,尤其是在需要凭证的情况下):

plaintext
Access-Control-Allow-Origin: *

2. 预检请求 (Preflight Request)
不满足“简单请求”条件的请求,就是“非简单请求”。例如,请求方法是 PUTDELETE,或者 Content-Typeapplication/json

对于这类请求,浏览器会先自动发送一个 OPTIONS 方法的“预检”请求到服务器,询问服务器是否允许接下来的实际请求。

预检请求的响应头需要包含更多信息:

plaintext
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://www.a.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400  // 预检请求的缓存时间(秒)

如果预检通过,浏览器才会发送真正的请求。

后端代码示例 (Node.js + Express):
可以使用 cors 中间件轻松实现。

bash
npm install cors
javascript
const express = require('express');
const cors = require('cors');
const app = express();

// 简单用法:允许所有源
// app.use(cors());

// 精细化配置
app.use(cors({
  origin: 'http://www.a.com', // 只允许这个源访问
  methods: ['GET', 'POST', 'PUT'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true // 如果需要携带 cookie,这个必须是 true
}));

app.get('/data', (req, res) => {
  res.json({ message: 'Hello from the API!' });
});

app.listen(3000, () => console.log('Server running on port 3000'));

方案二:代理服务器 (Proxy) 【非常实用】

你无法控制目标服务器(例如,请求一个第三方的 API)时,代理是最佳选择。

核心思想:
利用“服务器之间通信没有同源策略限制”这一特点。

  1. 前端(例如 http://www.a.com)不直接请求目标服务器(http://api.b.com)。
  2. 前端请求自己的同源服务器(http://www.a.com/api)。
  3. 自己的服务器收到请求后,再由它作为“代理”,去请求真正的目标服务器(http://api.b.com)。
  4. 服务器拿到数据后,再返回给前端。

实现方式:

  • 开发环境:现代前端框架(如 Vue CLI, Create React App, Vite)都内置了开发服务器代理功能,配置非常简单。
    • Vite 示例 (vite.config.js)
      javascript
      export default {
        server: {
          proxy: {
            // 字符串简写
            '/api': 'http://api.b.com',
            // 选项写法
            '/api': {
              target: 'http://api.b.com',
              changeOrigin: true, // 必须设置为 true
              rewrite: (path) => path.replace(/^\/api/, '') // 去掉路径中的 /api
            }
          }
        }
      }
  • 生产环境:通常使用 Nginx 作为反向代理。
    • Nginx 配置示例 (nginx.conf)
      plaintext
      server {
          listen 80;
          server_name www.a.com;
      
          location / {
              # 前端静态文件
              root /path/to/frontend/dist;
              try_files $uri $uri/ /index.html;
          }
      
          location /api/ {
              # 代理所有 /api/ 开头的请求
              proxy_pass http://api.b.com/;
              proxy_set_header Host api.b.com;
              proxy_set_header X-Real-IP $remote_addr;
          }
      }

方案三:JSONP (JSON with Padding) 【已过时,不推荐】

核心思想:
利用 <script> 标签的 src 属性不受同源策略限制。

  1. 前端定义一个回调函数(例如 handleData)。
  2. 通过 <script> 标签向服务器发送请求,并在 URL 中带上这个回调函数名(例如 ?callback=handleData)。
  3. 服务器收到请求后,不返回纯粹的 JSON 数据,而是返回一段 JavaScript 代码,内容是调用这个回调函数,并将 JSON 数据作为参数传入。例如 handleData({ "name": "Alice" })
  4. 浏览器加载并执行这段代码,前端定义的 handleData 函数就被调用了。

缺点:

  • 只支持 GET 请求:因为 <script> 标签只能发送 GET 请求。
  • 安全性差:容易遭受 XSS 攻击,因为服务器可以返回任意的 JavaScript 代码。
  • 错误处理不便

由于 CORS 的普及,JSONP 现在已经很少使用了,不建议在新项目中使用


总结与建议

方案 优点 缺点 适用场景
CORS W3C标准、安全、支持所有HTTP方法、功能强大 需要服务器端配置支持 首选方案,尤其是自己能控制后端API时。
代理 (Proxy) 无需修改目标服务器、安全、可以解决任何跨域问题 需要额外配置一台代理服务器、增加了一次网络请求 当前端无法控制目标服务器API时(如调用第三方服务)。
JSONP 兼容性好(支持老旧浏览器) 只支持GET、不安全、已过时 基本不再使用,仅用于兼容非常古老的项目。

简单来说:

  • 能改后端代码? -> 用 CORS
  • 不能改后端代码? -> 用 代理
  • 忘了 JSONP 吧,除非你在维护一个古董项目。
00:00
00:00