前端跨域问题--解析与实战
引言
在现代网络应用中,跨域问题是一个常见的挑战。由于浏览器的同源策略,限制了从不同源(域名、协议或端口)进行资源共享,这就造成了跨域访问的限制。跨域资源共享(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
文件内容
- public/index.html - 这是服务 A 将托管的静态 HTML 文件,用于发起跨域请求。
- serverA.js - 这个文件包含了服务 A 的代码,用于托管静态页面。
- 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');
});
如何运行项目
- 打开命令行界面,并导航到
cross-origin-project
文件夹。 - 运行
npm init -y
来初始化 Node.js 项目,并通过npm install express cors
安装必要的依赖。 - 在两个不同的命令行窗口中,分别启动服务 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
不能设置为 *
当 Credentials
为 true
。
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: 安装扩展
-
Chrome 浏览器:
- 打开 Chrome Web Store。
- 在搜索框中输入 “CORS Unblock”。
- 找到扩展,点击 “添加至 Chrome”。
-
Firefox 浏览器:
- 打开 Firefox Add-ons 网站。
- 搜索 “CORS Unblock”。
- 选择扩展并点击 “添加到 Firefox”。
步骤 2: 使用扩展
安装扩展后,你会在浏览器的工具栏中看到 CORS Unblock 的图标。你可以通过以下方式使用它:
-
激活/禁用 CORS Unblock:
- 点击浏览器工具栏中的 CORS Unblock 图标。
- 通常,扩展会有一个简单的开关按钮,允许你快速激活或禁用跨源请求限制。
- 当扩展被激活时,图标通常会变色(例如变为绿色),表示现在跨源请求的限制被禁用。
-
刷新页面:
- 在激活 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 头部。这是最安全和最可靠的解决方案。