前端跨域问题--解析与实战

时间:2024-07-06 07:00:18

引言

在现代网络应用中,跨域问题是一个常见的挑战。由于浏览器的同源策略,限制了从不同源(域名、协议或端口)进行资源共享,这就造成了跨域访问的限制。跨域资源共享(CORS)是一种技术,它允许网页上的脚本能够与不同源的服务器交互,是解决这一问题的关键技术。

跨域资源共享(CORS)简述

CORS 是一种网络安全协议,它定义了在服务器和客户端之间如何安全地实现跨源访问。通过在服务器端设置特定的HTTP头部,CORS 允许开发者指定哪些外部资源是可以被网页访问的,大幅度提升了网络应用的互操作性和安全性。

CORS 概念解析

什么是同源策略?

同源策略是浏览器的一项安全功能,它限制了一个源中的文档或脚本如何与另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全措施,不允许不同源之间的读取数据。

CORS 是如何工作的?

CORS 通过在服务器端返回一系列 CORS 响应头来工作,如 Access-Control-Allow-Origin,这些头信息指示浏览器允许哪些网站通过脚本访问该服务器的资源。如果 CORS 头信息缺失或值不正确,浏览器将阻止任何跨源请求。

实现 CORS 的常用方法

使用 JSONP 解决跨域问题

JSONP(JSON with Padding)是一种早期用于绕过同源策略的技术,它通过动态添加 <script> 标签来请求一个带有回调函数的URL,从而实现跨域请求。这种方法只支持GET请求。

实现 CORS:基本概念和步骤

CORS 的实现涉及在服务器端设置适当的HTTP头。例如,Access-Control-Allow-Origin 可以被设置为特定的域名或星号(*),表示允许所有域名的跨域请求。

通过 Express.js 设置 CORS

基本 CORS 设置

在 Express.js 中,可以使用 cors 中间件来简化 CORS 的配置。例如,可以全局配置应用以接受来自某个特定源的请求:

const cors = require('cors');
app.use(cors({
    origin: 'http://example.com'
}));

高级 CORS 设置(携带凭证、允许多种 HTTP 方法等)

对于需要更复杂的 CORS 配置,如支持凭证或多种HTTP方法,可以详细配置 cors 中间件的选项:

app.use(cors({
    origin: 'http://example.com',
    methods: ['GET', 'POST'],
    credentials: true
}));

Node.js 项目实战演练

基础文件和目录结构

首先,创建以下目录和文件结构:

cross-origin-project/
│
├── public/
│   └── index.html
│
├── serverA.js
└── serverB.js
文件内容
  1. public/index.html - 这是服务 A 将托管的静态 HTML 文件,用于发起跨域请求。
  2. serverA.js - 这个文件包含了服务 A 的代码,用于托管静态页面。
  3. serverB.js - 这个文件包含了服务 B 的代码,用于提供 API 并处理 CORS。

服务 A:提供静态 HTML 页面

服务 A 托管静态 HTML 页面,允许用户通过按钮触发跨域请求。服务运行在端口 3001。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cross-Origin Example</title>
<script>
async function fetchData() {
    try {
        const response = await fetch('http://localhost:3002/data', {
            method: 'GET',
            credentials: 'include', // 启用凭证
            headers: {
                'X-Custom-Header': 'value' // 添加不被允许的头部
            }
        });
        const data = await response.json();
        document.getElementById('data').textContent = JSON.stringify(data);
    } catch (error) {
        console.error('Error:', error);
    }
}
</script>
</head>
<body>
<h1>Cross-Origin Resource Sharing (CORS) Test</h1>
<button onclick="fetchData()">Fetch Data</button>
<p id="data"></p>
</body>
</html>

将以下 JavaScript 代码保存到 serverA.js 文件中。这段代码配置了一个 Express.js 服务器来托管 public 目录下的静态文件。

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

// 提供静态文件
app.use(express.static('public'));

app.listen(3001, () => {
    console.log('Server A running on http://localhost:3001');
});

服务 B:提供 API 数据

将以下 JavaScript 代码保存到 serverB.js 文件中。这段代码创建了另一个 Express.js 服务器,它提供一个 API 端点并配置了 CORS 以允许来自服务 A 的跨域请求。

Node.js 配置(服务 B):

const express = require('express');
const cors = require('cors');
const app = express();

// 配置 CORS 策略
app.use(cors({
    origin: 'http://localhost:3001', // 允许来自 3001 端口的跨域请求
    methods: ['GET'],
    credentials: true, // 允许跨域请求包含凭证信息
    allowedHeaders: ['Content-Type', 'X-Custom-Header'] // 明确允许自定义头部
}));

app.get('/data', (req, res) => {
    const allowedOrigins = ['http://localhost:3001'];
    const origin = req.headers.origin;
    
    if (allowedOrigins.includes(origin)) {
        res.json({ message: 'Successfully fetched cross-origin data from server B.' });
    } else {
        res.status(403).send('Access denied');
    }
});

app.listen(3002, () => {
    console.log('Server B running on http://localhost:3002');
});

如何运行项目

  1. 打开命令行界面,并导航到 cross-origin-project 文件夹。
  2. 运行 npm init -y 来初始化 Node.js 项目,并通过 npm install express cors 安装必要的依赖。
  3. 在两个不同的命令行窗口中,分别启动服务 A 和服务 B:
    • 运行 node serverA.js 启动服务 A。
    • 运行 node serverB.js 启动服务 B。

访问 http://localhost:3001 在浏览器中查看 HTML 页面,并尝试发起跨域请求。此时应该能看到成功信息打印成功。

跨域原因列举

CORS(跨域资源共享)设置可能因为多种原因而失败,导致跨域请求无法成功。这些原因包括配置错误、环境限制或者浏览器的安全策略。下面是一些常见的原因导致跨域设置不成功:

1. 错误的源设置

如果 CORS 策略中设置的 Access-Control-Allow-Origin 头不正确,如不匹配请求的源(origin),跨域请求将被拒绝。例如,如果服务器只允许来自 http://localhost:3001 的请求,但请求实际来自 http://127.0.0.1:3001(尽管这两个地址指向同一个位置,但对 CORS 来说它们是不同的源)。

2. 忽略发送凭证

如果在 AJAX 请求中设置了 withCredentials = true,但服务器端的 CORS 策略没有正确设置 Access-Control-Allow-Credentials: true,则会导致跨域请求失败。同时,Access-Control-Allow-Origin 不能设置为 *Credentialstrue

3. 不支持的请求方法

如果请求使用了 CORS 策略中未明确允许的 HTTP 方法(如 PUT、PATCH、DELETE 等),请求也会被拒绝。只有正确声明了 Access-Control-Allow-Methods 头部,才能支持额外的方法。

4. 不允许的头部字段

如果跨域请求中包含了服务器未通过 Access-Control-Allow-Headers 明确允许的头部字段,请求将被浏览器拦截。这常见于自定义头部,如 X-Custom-Header

5. 预检请求失败

复杂请求(如带自定义头或者特定方法的请求)首先会发送一个预检(OPTIONS)请求,如果预检请求没有得到正确处理(如未返回允许的方法或头部),实际请求不会被发送。

6. 浏览器的安全策略

不同浏览器对于跨域请求的处理可能略有差异,有些浏览器的安全策略可能更加严格。此外,浏览器的某些配置或扩展程序(如广告拦截器或安全插件)可能阻止跨域请求。

7. 网络问题或服务器不可达

服务器配置正确,但由于网络问题或服务器宕机,导致请求无法到达服务器或服务器无法正确响应。

8. 错误的端口或协议

有时开发人员可能忽视了端口或协议的差异,这些细微的差别也会导致跨域错误,因为 CORS 是对协议、域名和端口都敏感的。

跨域行为实战

我们可以在先前给出的服务 A 和服务 B 的代码基础上,通过修改设置和观察结果来测试各种导致 CORS 设置不成功的情况。以下是针对每种情况的修改方法和预期的测试结果:

1. 错误的源设置

修改服务 B 来只接受来自 http://127.0.0.1:3001 的请求,而我们的请求实际上来自 http://localhost:3001

修改后的服务 B (api.js):

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({
    origin: 'http://127.0.0.1:3001', // 错误的源设置
    methods: ['GET'],
    credentials: true
}));

app.get('/data', (req, res) => {
    res.json({ message: 'Successfully fetched cross-origin data from server B.' });
});

app.listen(3002, () => {
    console.log('Server B running on http://localhost:3002');
});

预期结果: 浏览器控制台将显示类似于 "No 'Access-Control-Allow-Origin' header is present on the requested resource" 的错误,表示请求的源不被允许。

2. 忽略发送凭证

删除或错误设置 Access-Control-Allow-Credentials,同时保留 credentials: 'include'

修改后的服务 B (api.js):

app.use(cors({
    origin: 'http://localhost:3001',
    methods: ['GET'],
    credentials: false // 错误地设置为 false
}));

预期结果: 浏览器控制台会显示错误,提示凭证不被允许,因为 credentials 属性需要服务器明确允许。

3. 不支持的请求方法

假设我们尝试发送一个 POST 请求,但服务 B 只允许 GET。

服务 A 发起 POST 请求 (public/index.html):

<script>
async function fetchData() {
    try {
        const response = await fetch('http://localhost:3002/data', {
            method: 'POST',
            credentials: 'include'
        });
        const data = await response.json();
        document.getElementById('data').textContent = JSON.stringify(data);
    } catch (error) {
        console.error('Error:', error);
    }
}
</script>

预期结果: 浏览器控制台会显示错误,指出使用了未被允许的方法。

4. 不允许的头部字段

发送包含自定义头部的请求,而服务 B 没有允许这个头部。

修改服务 A (public/index.html):

<script>
async function fetchData() {
    try {
        const response = await fetch('http://localhost:3002/data', {
            method: 'GET',
            credentials: 'include',
            headers: {
                'X-Custom-Header': 'value' // 添加不被允许的头部
            }
        });
        const data = await response.json();
        document.getElementById('data').textContent = JSON.stringify(data);
    } catch (error) {
        console.error('Error:', error);
    }
}
</script>

预期结果: 浏览器控制台将显示错误,提示 "Request header field X-Custom-Header is not allowed"。

通过这些修改和测试,你可以更好地理解 CORS 设置可能出现的问题和如何解决这些问题。每次修改后,确保重启服务并清空浏览器缓存,以确保测试结果的准确性。

服务端不做修改,浏览器如何避免cors

在 Web 开发中,浏览器的同源策略保护用户免受恶意网站的攻击,该策略禁止一个域的脚本与另一个域的资源进行交互。这意味着如果 CORS(跨源资源共享)策略不允许特定的跨源请求,那么在浏览器端通常没有简单的方法可以"避免"或"绕过" CORS 错误。这是一种安全机制,不应被绕过,特别是在生产环境中。

然而,对于开发测试目的,以下是几种常用的方法来处理或绕过浏览器的 CORS 限制:

1. 使用代理服务器

在开发环境中,可以设置一个代理服务器,它接收你的请求,向目标服务器发送请求,并将响应返回给你的应用。这种方式常用于前端开发中,例如使用创建 React 应用的 create-react-app 工具,可以在 package.json 中添加一个代理配置:

"proxy": "http://localhost:3002",

这样,所有对 /data 的请求将会被代理到 http://localhost:3002/data,而浏览器会认为这些请求仍然是同源的。

2. 使用浏览器插件

有些浏览器插件可以禁用或修改 CORS 策略,例如 "CORS Unblock" 等。这些插件通常用于开发和测试,但绝对不推荐在生产环境或浏览常规网站时使用,因为这会降低你的网络安全防护。

如何使用 CORS Unblock
步骤 1: 安装扩展
  1. Chrome 浏览器:

    • 打开 Chrome Web Store。
    • 在搜索框中输入 “CORS Unblock”。
    • 找到扩展,点击 “添加至 Chrome”。
  2. Firefox 浏览器:

    • 打开 Firefox Add-ons 网站。
    • 搜索 “CORS Unblock”。
    • 选择扩展并点击 “添加到 Firefox”。
步骤 2: 使用扩展

安装扩展后,你会在浏览器的工具栏中看到 CORS Unblock 的图标。你可以通过以下方式使用它:

  1. 激活/禁用 CORS Unblock:

    • 点击浏览器工具栏中的 CORS Unblock 图标。
    • 通常,扩展会有一个简单的开关按钮,允许你快速激活或禁用跨源请求限制。
    • 当扩展被激活时,图标通常会变色(例如变为绿色),表示现在跨源请求的限制被禁用。
  2. 刷新页面:

    • 在激活 CORS Unblock 后,刷新你正在工作的网页。现在,页面上的脚本应该能够发出并接收原本因 CORS 策略而被阻止的请求。
步骤 3: 监控和调试
  • 使用浏览器的开发者工具(通常可以通过右键点击页面并选择“检查”来打开)来监控网络请求和响应。
  • 检查网络标签,确保之前因 CORS 错误被阻止的请求现在是否成功。

3. 配置浏览器启动参数

对于 Chrome 浏览器,可以通过添加启动参数来禁用安全特性,这包括 CORS 检查。可以使用以下命令启动 Chrome(仅在本地开发时使用):

chrome.exe --disable-web-security --user-data-dir="C:/ChromeDevSession"

这将开启一个新的 Chrome 会话,其中禁用了跨域安全检查。注意,这种方法仅应在完全控制的开发环境中使用,且必须保证不会浏览任何潜在的恶意网站。

4.使用抓包工具

抓包工具是一类用于捕获、分析和修改计算机网络通信流量的软件工具。这些工具允许开发人员和网络管理员监控应用程序的网络交互,查看请求和响应的详细信息,进行性能评估,以及在开发和测试阶段修改数据流以进行调试。抓包工具对于诊断网络问题、优化性能和确保安全性都具有重要作用。常用的抓包工具有 「Fiddler」、「Charles」、「Wireshark」 等。

使用抓包工具代理请求的方式来处理跨域问题,通常涉及将 Web 网络请求通过工具进行中间代理,实现对请求和响应的监控、分析和修改。通过抓包工具的 rewrite 能力重写网络响应,给存在跨域的接口都添加上 CORS 相关配置,来解决跨域问题。

下面将简单介绍 Charles 工具的操作步骤:

「步骤一」:打开 Charles 工具栏 Tools --> Rewrite

「步骤二」:添加 Rewrite 配置

「步骤三」:Add 时添加 CORS 配置响应头

通过完成上述的三个步骤,我们现在就可以直接访问跨域接口了,可以看到所有的接口响应上都已经被添加了 Access-Control-Allow-Origin:* CORS 属性。

5. 修改服务器配置(理想方式)

虽然这不是浏览器端的解决方案,但修改服务器以发送正确的 CORS 头部是解决跨域问题的最佳实践。这包括正确配置 Access-Control-Allow-Origin 和相关的 CORS 头部。这是最安全和最可靠的解决方案。