【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-OriginAccess-Control-Allow-MethodsAccess-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-Type3. 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');
});运行流程:
- 启动后端服务(
node server.js) - 启动前端服务(
http-server) - 访问
http://localhost:3000 - 控制台将输出用户数据
关键点:
- 使用
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头、优化预检请求和处理异常情况,可以有效解决跨域问题,提升开发效率和系统安全性。
评论已关闭