浏览器同源策略与跨域问题

文章目录

  • 浏览器同源策略与跨域详解
    • [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. 什么是跨域?

当一个资源尝试请求另一个 不同源 的资源时,就会产生 跨域请求

受同源策略影响,浏览器默认会阻止以下跨域行为:

  1. 无法读取 非同源的 Cookie、LocalStorage 和 IndexDB。
  2. 无法操作 非同源的 DOM。
  3. 无法获取 非同源 AJAX/Fetch 请求的响应结果。

注:<script><img><link> 等标签允许跨域加载资源,这是 CDN 等技术的基础。


4. 如何实现跨域资源共享?

在实际开发中,我们经常需要跨域请求数据。以下是几种常见的解决方案:

A. CORS (跨域资源共享) 深度解析

这是目前最通用的方案。通过在服务器端设置特定的 HTTP 响应头,告诉浏览器该接口允许指定的域名访问。

  • 关键响应头: Access-Control-Allow-Origin: * (或指定域名)

CORS 是 W3C 标准,它允许浏览器向跨源服务器发出 XMLHttpRequestFetch 请求。它将跨域请求分为 简单请求非简单请求

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 -> 触发跨域拦截
  • 有代理时:
    1. 浏览器请求本地服务器:localhost:8080/api/user(同源,不触发跨域)。
    2. 本地开发服务器接收请求,根据配置将其转发api.remote.com/api/user(服务器间通信,不受同源策略限制)。
    3. 开发服务器拿到结果,再回传给浏览器。

2. 关键配置示例

webpack.config.jsvue.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 devnpm 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)?

反向代理 代理的是服务端

对于用户来说,反向代理服务器就像是原始服务器。用户发出的请求直接指向反向代理名称空间,然后由反向代理根据配置,将请求转发给内部网络上的后端服务器。

  • 工作流程:

    1. 用户访问 https://your-website.com
    2. 请求到达 Nginx(反向代理服务器)。
    3. Nginx 内部决定将请求转发给后端的 Server AServer BServer C
    4. 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)。
相关推荐
2301_7990730221 小时前
基于 Next.js + 火山引擎 AI 的电商素材智能生成工具实战——字节跳动前端训练营成果
javascript·人工智能·火山引擎
kyriewen111 天前
项目做了一半想重写?这套前端架构让你少走3年弯路
前端·javascript·chrome·架构·ecmascript·html5
爱折腾的军哥1 天前
首发 | OpenTaiji WFGY 防幻觉系统:让 AI Agent 不再"胡说八道"
javascript
颜酱1 天前
从零实现「拍照记单词」小应用(可复刻版)
前端·javascript·人工智能
大猫会长1 天前
AudioContext给音频提高音量
前端·javascript·音视频
WayneYang1 天前
JavaScript ES6+ (ES2015~ES2024) 全特性整理
前端·javascript
JustNow_Man1 天前
Bun 常用命令速查清单(TypeScript 编译篇)
前端·javascript·typescript
|晴 天|1 天前
从零打造现代化个人博客:Vue 3 + TypeScript + Element Plus 完整实战
javascript·css·chrome·typescript·html5·webstorm
lion101 天前
简单Canvas指纹示例
javascript
M ? A1 天前
Vue v-bind 转 React:VuReact 怎么处理?
前端·javascript·vue.js·经验分享·react.js·面试·vureact