文章目录
- 浏览器同源策略与跨域详解
-
- [1. 什么是同源策略 (Same-Origin Policy)?](#1. 什么是同源策略 (Same-Origin Policy)?)
- [2. 为什么要有同源策略?](#2. 为什么要有同源策略?)
- [3. 什么是跨域?](#3. 什么是跨域?)
- [4. 如何实现跨域资源共享?](#4. 如何实现跨域资源共享?)
-
- [A. CORS (跨域资源共享) 深度解析](#A. CORS (跨域资源共享) 深度解析)
-
- [1. 工作原理](#1. 工作原理)
- [2. 关键响应头说明](#2. 关键响应头说明)
- [3. 代码示例](#3. 代码示例)
-
- [**Node.js (Express)**](#Node.js (Express))
- [B. JSONP (JSON with Padding) 深度解析](#B. JSONP (JSON with Padding) 深度解析)
-
- [1. 核心原理](#1. 核心原理)
- [2. 代码实现示例](#2. 代码实现示例)
-
- [**前端 (HTML/JavaScript)**](#前端 (HTML/JavaScript))
- [C. 代理服务器 (Proxy)](#C. 代理服务器 (Proxy))
-
- [Webpack devServer 代理原理深度解析](#Webpack devServer 代理原理深度解析)
- [D. Nginx 反向代理](#D. Nginx 反向代理)
- [Nginx 与 反向代理详解](#Nginx 与 反向代理详解)
-
- [1. 什么是 Nginx?](#1. 什么是 Nginx?)
- [2. 什么是代理 (Proxy)?](#2. 什么是代理 (Proxy)?)
- [3. 什么是反向代理 (Reverse Proxy)?](#3. 什么是反向代理 (Reverse Proxy)?)
- [4. 为什么要使用反向代理?](#4. 为什么要使用反向代理?)
-
- [A. 解决跨域 (Cross-Origin)](#A. 解决跨域 (Cross-Origin))
- [B. 负载均衡 (Load Balancing)](#B. 负载均衡 (Load Balancing))
- [C. 提高安全性](#C. 提高安全性)
- [D. 动静分离](#D. 动静分离)
- [5. 一句话总结](#5. 一句话总结)
浏览器同源策略与跨域详解
1. 什么是同源策略 (Same-Origin Policy)?
同源策略 (SOP) 是浏览器最核心、最基础的安全功能。它要求两个 URL 的 协议 (Protocol) 、域名 (Host) 和 端口 (Port) 必须完全相同。
| URL 1 | URL 2 | 结果 | 原因 |
|---|---|---|---|
https://google.com/a |
https://google.com/b |
同源 | 协议、域名、端口全一致 |
http://google.com |
https://google.com |
跨域 | 协议不同 (HTTP vs HTTPS) |
https://google.com |
https://baidu.com |
跨域 | 域名不同 |
https://google.com:80 |
https://google.com:8080 |
跨域 | 端口不同 |
2. 为什么要有同源策略?
它的存在是为了 保护用户数据的安全,防止恶意网站窃取隐私。
场景模拟:
如果你登录了网银网站
bank.com,浏览器存下了你的登录 Cookie。此时你打开了一个恶意网站evil.com。
- 如果没有同源策略:
evil.com的脚本可以轻易读取bank.com的 Cookie,并冒充你向银行发送转账请求。- 有了同源策略: 浏览器限制了非同源脚本的权限,从而封堵了这类安全漏洞。
3. 什么是跨域?
当一个资源尝试请求另一个 不同源 的资源时,就会产生 跨域请求。
受同源策略影响,浏览器默认会阻止以下跨域行为:
- 无法读取 非同源的 Cookie、LocalStorage 和 IndexDB。
- 无法操作 非同源的 DOM。
- 无法获取 非同源 AJAX/Fetch 请求的响应结果。
注:<script>、<img>、<link> 等标签允许跨域加载资源,这是 CDN 等技术的基础。
4. 如何实现跨域资源共享?
在实际开发中,我们经常需要跨域请求数据。以下是几种常见的解决方案:
A. CORS (跨域资源共享) 深度解析
这是目前最通用的方案。通过在服务器端设置特定的 HTTP 响应头,告诉浏览器该接口允许指定的域名访问。
- 关键响应头:
Access-Control-Allow-Origin: *(或指定域名)
CORS 是 W3C 标准,它允许浏览器向跨源服务器发出 XMLHttpRequest 或 Fetch 请求。它将跨域请求分为 简单请求 和 非简单请求。
1. 工作原理
- 简单请求: 浏览器直接发出请求,并在查询头中带上
Origin。服务器返回时如果包含Access-Control-Allow-Origin且匹配,则成功。 - 非简单请求(如 PUT、DELETE 或 Content-Type 为 application/json): 浏览器会先发送一个 预检请求 (Preflight) ,即
OPTIONS请求。只有收到服务器确认允许的响应后,浏览器才会发送真正的实际请求。
2. 关键响应头说明
| 响应头 | 作用 |
|---|---|
| Access-Control-Allow-Origin | 允许跨域访问的域名。* 代表所有,但若要携带 Cookie 则必须指定具体域名。 |
| Access-Control-Allow-Methods | 允许的 HTTP 方法,如 GET, POST, PUT, DELETE, OPTIONS。 |
| Access-Control-Allow-Headers | 允许前端发送的自定义请求头。 |
| Access-Control-Allow-Credentials | 是否允许发送 Cookie。设为 true 时,Origin 不能为 *。 |
| Access-Control-Max-Age | 预检请求(OPTIONS)的有效期,单位为秒,减少频繁的预检。 |
3. 代码示例
Node.js (Express)
使用 cors 中间件是最简单的方式:
javascript
const express = require('express');
const cors = require('cors');
const app = express();
// 配置允许特定域名跨域并允许 Cookie
app.use(cors({
origin: '[https://www.your-frontend.com](https://www.your-frontend.com)',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE']
}));
app.get('/data', (req, res) => {
res.json({ message: "CORS 配置成功!" });
});
app.listen(3000);
B. JSONP (JSON with Padding) 深度解析
JSONP 并不是一种官方的标准协议,而是一种利用浏览器"历史遗留特性"实现的 跨域技巧。
利用 <script> 标签不受跨域限制的"漏洞"来获取数据。
- 优点: 兼容性极好(老旧浏览器也支持)。
- 缺点: 仅支持 GET 请求,且存在安全隐患。
1. 核心原理
浏览器虽然限制 AJAX 跨域,但允许 <script> 标签加载任何来源的 JS 脚本。
- 前端: 声明一个处理数据的函数,并通过
<script>标签请求后端,同时把函数名通过 URL 参数(通常是callback=xxx)传给后端。 - 后端: 接收到请求后,将数据包装在函数调用中,返回一段像
callbackName({"id": 1, "name": "Gemini"})这样的字符串。 - 执行: 脚本加载完成后,浏览器会立即执行这段 JS 代码,从而触发前端定义的函数,拿到数据。
2. 代码实现示例
前端 (HTML/JavaScript)
html
<!DOCTYPE html>
<html>
<body>
<h1>JSONP 测试</h1>
<div id="result">等待数据...</div>
<script>
// 1. 定义回调函数,用来处理接收到的数据
function handleResponse(data) {
console.log("收到跨域数据:", data);
document.getElementById('result').innerText = "获取成功:" + data.message;
}
// 2. 动态创建 script 标签发送请求
const script = document.createElement('script');
script.src = '[http://api.example.com/user?id=123&callback=handleResponse](http://api.example.com/user?id=123&callback=handleResponse)';
document.body.appendChild(script);
// 3. 请求完成后移除标签(可选,保持页面整洁)
script.onload = () => script.remove();
</script>
</body>
</html>
C. 代理服务器 (Proxy)
同源策略仅存在于浏览器端,服务器与服务器之间通信不受此限制。
- 原理: 前端请求自己的同源服务器,再由该服务器转发请求到真正的后端接口。
- 常见实现: Webpack 的
devServer或 Nginx 反向代理。
Webpack devServer 代理原理深度解析
1. 核心原理:中转代理 (Intermediary Proxy)
当你配置了 devServer.proxy,Webpack 内部的 http-proxy-middleware 会在本地开发服务器(通常是 localhost:8080)上启动一个监听器。
- 没有代理时: 浏览器(
localhost:8080)直接请求api.remote.com-> 触发跨域拦截。 - 有代理时:
- 浏览器请求本地服务器:
localhost:8080/api/user(同源,不触发跨域)。 - 本地开发服务器接收请求,根据配置将其转发 给
api.remote.com/api/user(服务器间通信,不受同源策略限制)。 - 开发服务器拿到结果,再回传给浏览器。
- 浏览器请求本地服务器:
2. 关键配置示例
在 webpack.config.js 或 vue.config.js 中,典型的配置如下:
javascript
module.exports = {
devServer: {
proxy: {
// 匹配所有以 /api 开头的请求
'/api': {
target: '[https://api.remote.com](https://api.remote.com)', // 目标真实服务器地址
changeOrigin: true, // 核心配置:修改请求头中的 Host
pathRewrite: { '^/api': '' }, // 路径重写:去掉多余的 /api 前缀
secure: false, // 如果是 https 接口且证书未认证,需设为 false
},
},
},
};
3. 核心参数详解
changeOrigin: true (最重要)
- 为什么要开启?
服务器(如 Nginx, Apache 或某些 Node 框架)通常会根据 HTTP 请求头中的Host字段来识别请求目标或进行安全校验。 - 效果对比:
false(默认): 转发请求时,Header 中的Host依然保持为前端地址localhost:8080。true: 代理服务器会将Host修改为目标地址api.remote.com。
- 目的: 实现"伪装"。让后端服务器认为这个请求是直接发给它的,从而避免因为
Host不匹配导致的权限验证失败或 404 路由问题。
pathRewrite
- 为什么要开启?
前端开发时,通常为了统一管理和区分静态资源请求,会给所有跨域接口加上一个特征前缀(例如/api/login)。但后端的真实接口路径可能并不包含这个/api。 - 效果:
- 配置
pathRewrite: { '^/api': '' }后,代理服务器在转发请求前,会通过正则匹配把路径开头的/api删掉。 - 请求流向:
浏览器请求 /api/login->代理服务器去除 /api->后端实际接收到 /login。
- 配置
4. devServer 的局限性
- 仅限开发阶段:
devServer是 Webpack 的一个开发辅助插件,它只在你运行npm run dev或npm run serve启动本地 Node 服务时才生效。 - 生产环境无效: 当你执行
npm run build将项目打包成静态 HTML/JS/CSS 文件后,这些 Node 逻辑就不存在了。此时将文件部署到线上服务器(如阿里云、腾讯云),原本的代理配置会直接失效。
5. 生产环境替代方案:Nginx 反向代理
在生产环境下,通常使用 Nginx 这种高性能 Web 服务器来实现类似的逻辑。它的配置语法与 devServer 非常相似:
nginx
server {
listen 80;
server_name your-website.com;
# 1. 处理前端静态资源
location / {
root /usr/share/nginx/html/dist;
index index.html;
try_files $uri $uri/ /index.html; # 支持单页面应用路由
}
# 2. 实现后端接口代理 (对应 devServer 的 proxy)
location /api/ {
# 对应 target
proxy_pass [http://api.remote.com/](http://api.remote.com/);
# 对应 changeOrigin
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
D. Nginx 反向代理
在生产环境下,通过 Nginx 将前端资源和后端 API 统一映射到同一个域名下,从而实现"逻辑同源"。
nginx
location /api/ {
proxy_pass http://backend_server:8080/;
}
Nginx 与 反向代理详解
1. 什么是 Nginx?
Nginx (发音同 "engine x") 是一款高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。
- 高性能: 它采用事件驱动(Event-driven)架构,能够支撑数以万计的并发连接,而消耗的内存却非常低。
- 多功能: 它既可以作为静态资源服务器 (部署前端代码),也可以作为负载均衡器 (分发流量),还可以作为安全网关。
2. 什么是代理 (Proxy)?
在理解"反向代理"之前,我们先看什么是"正向代理":
- 正向代理 (Forward Proxy): 代理的是客户端 。
- 例子: 你想上某个无法直接访问的网站,你找了个"梯子"。服务器只知道代理服务器来访问了,不知道背后的真实客户端是谁。
- 关键词: 隐藏客户端。
3. 什么是反向代理 (Reverse Proxy)?
反向代理 代理的是服务端。
对于用户来说,反向代理服务器就像是原始服务器。用户发出的请求直接指向反向代理名称空间,然后由反向代理根据配置,将请求转发给内部网络上的后端服务器。
-
工作流程:
- 用户访问
https://your-website.com。 - 请求到达 Nginx(反向代理服务器)。
- Nginx 内部决定将请求转发给后端的
Server A、Server B或Server C。 - Nginx 拿到结果后,再返回给用户。
- 用户访问
-
用户视角: 用户完全不知道后端服务器的存在,甚至不知道后端有几台服务器。
4. 为什么要使用反向代理?
A. 解决跨域 (Cross-Origin)
正如之前讨论的,通过 Nginx 将前端静态文件和后端 API 接口配置在同一个域名/端口下,利用服务器转发绕过浏览器的同源策略限制。
B. 负载均衡 (Load Balancing)
如果你的网站流量巨大,一台服务器扛不住,Nginx 可以将请求均匀地分配到多台服务器上。如果其中一台挂了,Nginx 会自动跳过它。
C. 提高安全性
后端服务器隐藏在内网中,不直接暴露公网 IP。Nginx 作为唯一的"大门",可以统一配置 SSL 证书(HTTPS)、防御 DDoS 攻击、限制请求频率等。
D. 动静分离
Nginx 处理静态资源(HTML, CSS, 图片)的速度极快。它可以直接拦截并返回静态文件,而只把需要动态计算的请求(API)转发给后端的 Java/Node/Python 服务器,极大减轻后端压力。
5. 一句话总结
- 正向代理: 保护并代表客户端(如:VPN)。
- 反向代理: 保护并代表服务端(如:Nginx)。