【Ajax】笔记-同源策略

'# 【Ajax】笔记-同源策略

一、背景与问题

在现代Web开发中,Ajax技术已成为前后端交互的核心手段。然而,浏览器出于安全考虑引入了同源策略(Same-Origin Policy),这使得跨域请求成为开发者必须面对的挑战。

同源策略的核心规则是:协议、域名、端口三者完全一致的请求才被允许。例如:

  • https://api.example.com 与 http://api.example.com 不同源(协议不同)
  • https://api.example.com 与 https://www.example.com 不同源(域名不同)
  • https://api.example.com:8080 与 https://api.example.com 不同源(端口不同)

这种限制虽然有效防范了跨站攻击(如XSS、CSRF),但也给实际开发带来困扰。例如:

  • 前端需要访问第三方API时
  • 微服务架构中不同子域之间需要通信
  • 前端需要与本地服务器进行调试

本文将从原理、实现、案例和实践角度,深入剖析同源策略的机制与应对方案。


二、基本原理

1. 同源策略的底层逻辑

浏览器通过检查请求的Origin头字段来判断是否同源。对于跨域请求,浏览器会自动添加Origin头,并向服务器发送预检请求(OPTIONS)来确认是否允许跨域访问。

关键字段:

  • Origin: 表示请求的来源(协议+域名+端口)
  • Access-Control-Allow-Origin: 服务器响应头,用于指定允许的源
  • Access-Control-Allow-Credentials: 控制是否允许携带凭证(如Cookie)

2. 预检请求(Preflight Request)

当请求满足以下条件时,浏览器会发起预检请求:

  • 使用POST方法
  • Content-Type为application/json、text/xml等
  • 设置X-Requested-With头
  • 设置withCredentials为true

预检请求使用OPTIONS方法,服务器需在响应中包含:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

3. 与CORS的关联

CORS(跨域资源共享)是同源策略的扩展机制,通过在响应头中添加白名单策略,允许特定源的跨域请求。CORS是当前主流的跨域解决方案。


三、环境准备

1. 开发工具

  • 前端:HTML、JavaScript(推荐使用Fetch API)
  • 后端:Node.js + Express(模拟跨域服务器)
  • 浏览器:Chrome/Firefox(支持CORS调试)

2. 项目结构(示例)

project/
├── client/             # 前端代码
│   └── index.html
│   └── script.js
├── server/             # 后端代码
│   └── server.js
└── README.md

四、核心实现

1. 简单跨域请求(失败案例)

<!-- client/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>CORS Demo</title>
</head>
<body>
  <button onclick="fetchData()">Fetch Data</button>
  <script>
    function fetchData() {
      fetch('https://api.example.com/data')
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.error('Error:', error));
    }
  </script>
</body>
</html>

关键点:

  • 直接请求https://api.example.com/data(假设该域未配置CORS)
  • 浏览器会触发预检请求,服务器未响应CORS头,导致请求被拦截

错误提示:

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

2. 合法CORS请求(成功案例)

服务器端配置(Node.js):

// server/server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({
  origin: 'http://localhost:3000',  // 允许的源
  methods: ['GET', 'POST'],
  allowedHeaders: ['Content-Type']
}));

app.get('/data', (req, res) => {
  res.json({ message: 'Hello from CORS server!' });
});

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

客户端请求:

// client/script.js
fetch('http://localhost:3000/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

关键点:

  • 使用fetch请求本地服务器
  • 服务器通过cors中间件自动处理CORS头
  • 无需手动添加Access-Control-Allow-*头

响应头示例:

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type

3. JSONP 跨域(历史方案)

<!-- client/index.html -->
<script>
  function handleResponse(data) {
    console.log('JSONP Response:', data);
  }
</script>
<script src="https://api.example.com/data?callback=handleResponse"></script>

服务器端响应:

// server/server.js
app.get('/data', (req, res) => {
  const callback = req.query.callback;
  res.send(`${callback}({ message: 'Hello from JSONP server!' })`);
});

关键点:

  • JSONP通过动态创建<script>标签实现跨域
  • 无需处理CORS头,但存在安全风险(可被任意网页调用)
  • 仅支持GET请求

五、完整案例

1. 前后端分离的CORS实现

场景: 前端(http://localhost:3000)请求后端(http://localhost:5000)的用户数据

前端代码(client/index.html):

<!DOCTYPE html>
<html>
<head>
  <title>CORS Example</title>
</head>
<body>
  <div id="content"></div>
  <script>
    fetch('http://localhost:5000/users', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json'
      }
    })
    .then(response => {
      if (!response.ok) throw new Error('Network response was not OK');
      return response.json();
    })
    .then(data => {
      const content = document.getElementById('content');
      content.innerHTML = JSON.stringify(data, null, 2);
    })
    .catch(error => {
      console.error('Error:', error);
    });
  </script>
</body>
</html>

后端代码(server/server.js):

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

app.use(cors({
  origin: 'http://localhost:3000',
  methods: ['GET', 'POST'],
  allowedHeaders: ['Content-Type']
}));

app.get('/users', (req, res) => {
  res.json({
    users: [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' }
    ]
  });
});

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

运行流程:

  1. 启动后端服务(node server.js)
  2. 启动前端服务(http-server)
  3. 访问http://localhost:3000
  4. 控制台将输出用户数据

关键点:

  • 使用fetch发送GET请求
  • 服务器通过CORS中间件自动处理跨域
  • 响应头包含必要的CORS字段

六、源码解析

1. fetch API 的行为

fetch(url, options)
  .then(response => {
    if (response.ok) {
      return response.json(); // 解析JSON响应
    }
    throw new Error('Request failed');
  })
  .catch(error => {
    console.error('Fetch error:', error);
  });

关键点:

  • fetch自动处理CORS预检请求
  • response.ok判断HTTP状态码是否在200-299范围
  • response.json()返回Promise对象

2. cors中间件的源码逻辑

function cors(options) {
  return (req, res, next) => {
    if (req.method === 'OPTIONS') {
      res.setHeader('Access-Control-Allow-Origin', options.origin);
      res.setHeader('Access-Control-Allow-Methods', options.methods.join(', '));
      res.setHeader('Access-Control-Allow-Headers', options.allowedHeaders.join(', '));
      res.end();
    } else {
      next();
    }
  };
}

关键点:

  • 对OPTIONS请求直接返回CORS头
  • 对普通请求调用next()继续处理

七、进阶使用

1. 带凭证的跨域请求

fetch('http://localhost:5000/secure-data', {
  method: 'GET',
  credentials: 'include', // 允许携带Cookie
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

关键点:

  • 设置credentials: 'include'
  • 服务器需配置Access-Control-Allow-Credentials: true
  • 仅允许特定源访问

2. 动态设置CORS源

app.use(cors({
  origin: (origin, callback) => {
    const allowedOrigins = ['http://localhost:3000', 'https://my-app.com'];
    if (allowedOrigins.includes(origin)) {
      callback(null, origin);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));

关键点:

  • 动态校验请求源
  • 防止任意源访问

八、性能与工程实践

1. 预检请求的性能优化

问题: 预检请求会增加网络开销,尤其在频繁请求时

解决方案:

  • 使用Cache-Control头缓存预检响应
  • 设置Access-Control-Max-Age控制缓存时间
  • 对于简单请求(GET/HEAD/POST),可直接发送数据

示例:

app.use(cors({
  maxAge: 86400, // 缓存24小时
  origin: 'http://localhost:3000'
}));

2. 安全性增强

关键配置:

  • 限制请求方法:methods: ['GET', 'POST']
  • 限制请求头:allowedHeaders: ['Content-Type', 'Authorization']
  • 限制请求源:origin: 'http://localhost:3000'
  • 禁用凭证:credentials: false(默认值)

风险提示:

  • 未设置Access-Control-Allow-Origin可能导致请求被拦截
  • 未设置Access-Control-Allow-Credentials可能导致Cookie泄露

九、常见问题与踩坑

1. 未配置CORS头导致的请求失败

错误示例:

// 错误:未设置CORS头
app.get('/data', (req, res) => {
  res.json({ message: 'Hello' });
});

解决方法:

  • 使用cors中间件
  • 手动设置响应头

    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');

2. 预检请求的OPTIONS方法未处理

错误示例:

// 错误:未处理OPTIONS请求
app.get('/data', (req, res) => {
  res.json({ message: 'Hello' });
});

解决方法:

  • 使用cors中间件自动处理
  • 手动处理OPTIONS请求

    app.options('/data', (req, res) => {
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
    res.end();
    });

3. JSONP的回调函数未定义

错误示例:

<script src="https://api.example.com/data?callback=handleResponse"></script>

解决方法:

  • 确保回调函数已定义

    function handleResponse(data) {
    console.log('JSONP Response:', data);
    }

十、最佳实践

1. 推荐方案

  • 优先使用CORS:现代浏览器支持良好,适合大多数场景
  • 使用代理服务器:前端请求本地服务器,后端转发请求(适合开发环境)
  • 避免JSONP:仅用于历史兼容性需求

2. 避免方案

  • 直接跨域请求:未配置CORS时会导致请求失败
  • 暴露敏感数据:未设置Access-Control-Allow-Origin可能导致数据泄露
  • 未处理预检请求:可能导致请求被拦截

3. 安全建议

  • 最小化CORS源:仅允许必要的源
  • 禁用凭证:除非必须携带Cookie
  • 设置Access-Control-Expose-Headers:暴露特定响应头

十一、总结

同源策略是浏览器安全机制的核心,它有效防止了跨站攻击,但也对跨域请求提出了挑战。通过CORS机制,开发者可以在保持安全性的前提下实现跨域通信。本文深入解析了同源策略的原理、实现方式、常见问题和最佳实践,提供了完整的代码示例和实际案例。

在实际开发中,应根据场景选择合适的解决方案:

  • 前后端分离时优先使用CORS
  • 微服务架构中使用代理服务器
  • 历史兼容性需求时可考虑JSONP

同时,务必注意安全配置,避免因CORS设置不当导致的数据泄露或安全漏洞。通过合理配置CORS头、优化预检请求和处理异常情况,可以有效解决跨域问题,提升开发效率和系统安全性。

最后修改于:2026年09月29日 16:14

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日