浏览器的同源策略

浏览器的同源策略 是 Web 安全的核心基石,它的本质是限制不同源的文档 / 脚本,对当前源的资源进行未授权的访问 。简单来说:非同源的页面,无法随意操作对方的 DOM、Cookie、LocalStorage,也无法发送跨域 AJAX 请求

一、 什么是 "同源"

同源的判定标准有 3 个核心要素三者必须完全一致,才被视为同源:

  1. 协议(Protocol) :如 http:https: 是不同源
  2. 域名(Host) :如 example.comsub.example.com 是不同源
  3. 端口(Port) :如 example.com:80example.com:8080 是不同源

同源判定示例

http://www.example.com:80 为基准,判断以下 URL 是否同源:

对比 URL 是否同源 原因
http://www.example.com:80 ✅ 是 协议、域名、端口完全一致
https://www.example.com:80 ❌ 否 协议不同(https vs http)
http://sub.example.com:80 ❌ 否 域名不同(sub.example vs www.example)
http://www.example.com:8080 ❌ 否 端口不同(8080 vs 80)
http://www.example.com ✅ 是 端口默认 80,三者一致

注意:

  • 域名的大小写不敏感Example.comexample.com 视为同源。
  • IP 地址和域名不互通,http://192.168.1.1http://example.com 是不同源。

二、 同源策略限制的核心场景

同源策略的限制主要针对 浏览器端的脚本(如 JavaScript),目的是防止恶意网站通过脚本窃取其他网站的敏感数据。核心限制分为以下几类:

1. 限制 DOM 操作

非同源的页面,无法通过脚本访问对方的 DOM 元素。

  • 场景 1 :如果 a.com 的页面中通过 <iframe> 嵌入了 b.com 的页面,那么 a.com 的 JS无法读取 b.com 页面的 document 对象、DOM 节点、表单数据。
  • 场景 2 :反过来,b.com 的 JS 也无法操作 a.com 的 DOM。
示例(禁止跨源 DOM 访问)
html 复制代码
<!-- a.com 的页面 -->
<iframe id="iframe" src="https://b.com"></iframe>
<script>
  const iframe = document.getElementById('iframe');
  // 尝试访问 iframe 内部的 DOM → 报错!
  iframe.onload = function() {
    // 跨源时,会抛出 "Uncaught DOMException: Blocked a frame from accessing a cross-origin frame."
    console.log(iframe.contentDocument.body); 
  }
</script>

2. 限制 Cookie/LocalStorage 等存储的访问

非同源的脚本,无法读取或修改对方域名下的 Cookie、LocalStorage、SessionStorage 等存储数据。

  • 示例a.com 的 JS 无法通过 document.cookie 获取 b.com 下的 Cookie;也无法通过 localStorage.getItem() 读取 b.com 的本地存储。
  • 例外 :如果 Cookie 的 Domain 属性设置为父域名(如 example.com),那么子域名(如 sub.example.com)的脚本可以访问该 Cookie(属于同源策略的宽松规则)。

3. 限制 AJAX/Fetch 请求(跨域请求拦截)

浏览器禁止脚本向非同源的服务器发送 AJAX 或 Fetch 请求,或者说,即使发送了请求,服务器响应的数据也会被浏览器拦截,无法被脚本获取。

示例(跨域 AJAX 请求被拦截)
javascript 复制代码
// a.com 的页面中,尝试请求 b.com 的接口 → 报错!
fetch('https://b.com/api/data')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.log(err)); 
  // 报错:"Access to fetch at 'https://b.com/api/data' from origin 'https://a.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."

关键说明:

  • 跨域请求本身是发送成功的(服务器能接收到请求并返回响应);
  • 浏览器会拦截响应,不允许脚本读取响应数据 ------ 这是同源策略的核心保护点。

三、 同源策略的 "例外情况"(允许跨源的操作)

同源策略并非 "一刀切",有一些场景是允许跨源操作的,主要是为了保障 Web 的基本功能:

  1. <script> 标签加载跨域 JS 文件 :比如加载 CDN 上的 jQuery、Vue 等库(src 指向非同源地址)。
    • 限制:脚本执行后,仍受同源策略约束,无法访问加载页面的 DOM / 存储。
  2. <link> 标签加载跨域 CSS 文件:同理,CSS 可以跨域加载,浏览器会解析并应用样式。
  3. <img> 标签加载跨域图片 :可以显示非同源的图片,支持防盗链(通过 Referer 控制)。
  4. <iframe> 嵌入跨域页面:可以嵌入,但父子页面的脚本无法互相访问 DOM。
  5. 表单提交(<form> :可以提交到非同源的服务器(比如登录表单提交到 auth.example.com),提交后会跳转页面,不受同源策略限制。
  6. WebSocket 连接 :WebSocket 协议(ws:///wss://)不受同源策略限制,可以直接连接非同源的服务器。

四、 如何突破同源策略

在实际开发中,经常需要跨域请求数据(如前后端分离项目、微服务),浏览器提供了合法的跨域方案,核心有以下几种:

1. CORS(跨域资源共享)

CORS(Cross-Origin Resource Sharing) 是最主流的跨域方案,由服务器端配置实现,浏览器自动适配。

  • 核心原理 :服务器在响应头中添加 Access-Control-Allow-Origin 等字段,明确告知浏览器 "允许哪些源的请求访问"。
  • 工作流程
    1. 浏览器发送跨域请求时,会先发送一个 OPTIONS 预检请求(复杂请求才会触发,如带自定义头、POST JSON),询问服务器是否允许跨域;
    2. 服务器响应预检请求,返回允许的源、方法、头等;
    3. 预检通过后,浏览器发送真实的请求;
    4. 服务器返回响应数据,浏览器允许脚本读取。
示例(服务器配置 CORS,以 Node.js/Express 为例)
javascript 复制代码
const express = require('express');
const app = express();

// 配置CORS,允许 a.com 跨域访问
app.use((req, res, next) => {
  // 允许的源:可以是具体域名,也可以是 *(允许所有源,不推荐生产环境)
  res.header('Access-Control-Allow-Origin', 'https://a.com');
  // 允许的请求方法
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  // 允许的请求头
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  // 允许携带Cookie(需要前后端同时配置)
  res.header('Access-Control-Allow-Credentials', 'true');
  // 处理OPTIONS预检请求
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  next();
});

// 接口路由
app.get('/api/data', (req, res) => {
  res.json({ message: '跨域请求成功!' });
});

app.listen(3000);

2. JSONP(JSON with Padding)

JSONP 是一种利用 <script> 标签不受同源策略限制 的 "hack 方案",仅支持 GET 请求

  • 核心原理
    1. 前端定义一个回调函数(如 handleJsonp);
    2. 通过 <script> 标签的 src 拼接请求参数和回调函数名,发送到服务器;
    3. 服务器返回一段 JS 代码,格式为 回调函数名(数据)
    4. 浏览器执行这段 JS 代码,回调函数被触发,获取到数据。
示例(JSONP 跨域请求)
html 复制代码
<!-- 前端页面(a.com) -->
<script>
  // 定义回调函数
  function handleJsonp(data) {
    console.log('JSONP获取到的数据:', data);
  }
</script>
<!-- 动态创建script标签,发送请求 -->
<script src="https://b.com/api/jsonp?callback=handleJsonp"></script>
javascript 复制代码
// 服务器端(b.com,Node.js/Express)
app.get('/api/jsonp', (req, res) => {
  const callback = req.query.callback; // 获取回调函数名
  const data = { message: 'JSONP跨域成功!' };
  // 返回 JS 代码:handleJsonp({...})
  res.send(`${callback}(${JSON.stringify(data)})`);
});

缺点:仅支持 GET 请求,存在 XSS 安全风险(服务器返回恶意代码会被执行),逐渐被 CORS 替代。

3. 反向代理(服务器代理)

反向代理前端无感知 的跨域方案,核心是利用同源策略只限制浏览器端,不限制服务器端的特点。

  • 核心原理
    1. 前端请求同源的后端代理服务器 (如 a.com/api/proxy);
    2. 代理服务器接收到请求后,转发请求 到目标非同源服务器(如 b.com/api/data);
    3. 代理服务器获取目标服务器的响应,再将数据返回给前端。
示例(Nginx 反向代理配置)
复制代码
# Nginx配置
server {
    listen 80;
    server_name a.com;

    # 代理 /api/proxy 路径到 b.com
    location /api/proxy {
        # 转发目标地址
        proxy_pass https://b.com/api/;
        # 携带请求头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

前端请求时,直接请求同源的代理地址:

javascript 复制代码
// 前端请求 a.com 的代理接口(同源)
fetch('http://a.com/api/proxy/data')
  .then(res => res.json())
  .then(data => console.log(data));

优点:支持所有请求方法,无前端代码修改,适合生产环境;缺点:需要配置服务器代理。

五、具体案例

我们以一个典型的电商平台为例,拆解跨域问题的产生过程:

1. 微服务拆分方案(真实场景)

假设电商平台拆分为以下核心微服务,每个服务独立部署:

微服务名称 服务地址(域名 + 端口) 核心功能
前端静态服务 https://shop.example.com 提供前端页面(Vue/React)
用户中心服务 https://user.example.com 登录、注册、用户信息查询
商品中心服务 https://goods.example.com 商品列表、商品详情查询
订单中心服务 https://order.example.com 创建订单、查询订单、支付
购物车服务 https://cart.example.com:8081 购物车增删改查(端口不同)

2. 跨域问题的具体触发场景

前端页面部署在 https://shop.example.com,当用户操作时,前端需要调用多个微服务的接口,每一次调用都会触发同源策略限制:

场景 1:用户登录(调用用户中心服务)
  • 前端操作:用户在 shop.example.com 页面输入账号密码,点击登录,前端通过 AJAX/Fetch 调用 https://user.example.com/api/login 接口;
  • 同源判定:shop.example.com(前端)和 user.example.com(后端)域名不同 → 非同源;
  • 浏览器行为:直接拦截响应,前端控制台报错 Access to fetch at 'https://user.example.com/api/login' from origin 'https://shop.example.com' has been blocked by CORS policy
场景 2:浏览商品(调用商品中心服务)
  • 前端操作:用户登录后,前端调用 https://goods.example.com/api/list 获取商品列表;
  • 同源判定:shop.example.comgoods.example.com 域名不同 → 非同源;
  • 浏览器行为:同样拦截响应,商品列表无法加载。

3.微服务架构中解决跨域的典型方案(业务视角)

在上述电商案例中,实际开发不会让每个微服务单独配置 CORS(维护成本高),而是采用更适配微服务的方案:

方案 1:API 网关统一处理跨域(主流方案)
  • 核心思路:在所有微服务前端部署一个 API 网关(如 Nginx、Spring Cloud Gateway、Kong),所有前端请求先经过网关,由网关统一配置 CORS 规则;
  • 具体落地:
    1. 前端只请求网关地址 https://gateway.example.com(与前端 shop.example.com 可配置为同源,或网关统一返回 CORS 头);
    2. 网关将请求转发到对应的微服务(用户 / 商品 / 订单);
    3. 网关在响应头中统一添加 Access-Control-Allow-Origin: https://shop.example.com 等 CORS 字段;
  • 优势:只需配置一次,所有微服务都受益,符合微服务 "统一入口" 的设计理念。
方案 2:微服务统一配置 CORS(兜底方案)
  • 核心思路:通过微服务框架的全局配置,给所有服务统一添加 CORS 响应头;
  • 举例(Spring Boot 微服务):所有微服务引入统一的跨域配置依赖,配置允许的源为 https://shop.example.com,允许的方法为 GET/POST/PUT/DELETE;
  • 劣势:每个微服务都要配置,若前端域名变更,需修改所有微服务的配置,维护成本高。

0voice · GitHub

相关推荐
薛定谔的猫198215 小时前
RAG(二)基于 LangChain+FAISS + 通义千问搭建轻量级 RAG 检索增强生成系统
运维·服务器·langchain
米高梅狮子16 小时前
4. Linux 进程调度管理
linux·运维·服务器
浩子智控19 小时前
电子产品设计企业知识管理
运维·服务器·eclipse·系统安全·硬件工程
以太浮标19 小时前
华为eNSP模拟器综合实验之-BGP路由协议的配置解析
服务器·开发语言·php
Xの哲學19 小时前
深入解析 Linux systemd: 现代初始化系统的设计与实现
linux·服务器·网络·算法·边缘计算
宠..19 小时前
优化文件结构
java·服务器·开发语言·前端·c++·qt
三两肉20 小时前
HTTP/1.1到HTTP/3:互联网通信协议的三代演进之路
网络·网络协议·http·http3·http2·http1.1
三两肉20 小时前
HTTP 缓存详解
网络协议·http·缓存
韩明君21 小时前
debian13学习笔记
服务器·笔记·学习
m0_737302581 天前
云服务器的技术架构:四层架构体系与核心组件协同
服务器