002-跨域:原理、机制与解决方案

跨域:原理、机制与解决方案

一、什么是跨域?

跨域(Cross-Origin) 是指浏览器出于安全考虑,限制网页脚本向不同源(协议+域名+端口) 的服务发起请求的行为。这是浏览器实现的同源策略(Same-Origin Policy) 所导致的安全限制。

同源策略三要素

  1. 协议相同(HTTP/HTTPS)
  2. 域名相同www.example.com)
  3. 端口相同(80/443)

示例

当前页面URL 请求URL 是否同源 原因
https://www.example.com https://www.example.com/api 协议、域名、端口相同
http://www.example.com https://www.example.com 协议不同(HTTP vs HTTPS)
https://example.com https://api.example.com 域名不同(主域 vs 子域)
https://www.example.com:8080 https://www.example.com 端口不同(8080 vs 443)

二、跨域场景分析

1. 常见跨域场景

  • 前端与API分离https://web.com 请求 https://api.com
  • 微服务架构https://service1.com 请求 https://service2.com
  • CDN资源https://main.com 加载 https://cdn.com/resource.js
  • 第三方服务https://myapp.com 接入 https://maps.google.com

2. 跨域限制范围

操作类型 是否允许跨域 示例
链接跳转 <a href="https://other.com">
资源嵌入 <img src="https://other.com/img.png">
表单提交 <form action="https://other.com">
AJAX请求 fetch('https://other.com/api')
Cookie访问 document.cookie 读取其他域名cookie
DOM操作 iframe.contentDocument 跨域访问

三、浏览器跨域判断机制

1. CORS(跨域资源共享)流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sequenceDiagram
participant Browser
participant Server

Browser->>Server: 发送跨域请求(带Origin头)
alt 简单请求
Server-->>Browser: 响应包含Access-Control-Allow-Origin
Browser->>Browser: 检查响应头是否匹配Origin
else 复杂请求
Browser->>Server: 先发送OPTIONS预检请求
Server-->>Browser: 返回CORS策略头
Browser->>Browser: 验证策略是否允许
Browser->>Server: 发送实际请求
Server-->>Browser: 返回实际响应
end

2. 核心响应头字段

响应头字段 作用 示例
Access-Control-Allow-Origin 允许的源 https://web.com*
Access-Control-Allow-Methods 允许的HTTP方法 GET, POST, PUT
Access-Control-Allow-Headers 允许的请求头 Content-Type, Authorization
Access-Control-Allow-Credentials 是否允许发送Cookie true
Access-Control-Max-Age 预检请求缓存时间 86400(1天)

四、跨域解决方案详解

1. CORS(跨域资源共享)

服务端配置示例(Node.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'https://web.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Max-Age', '86400');

if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}

next();
});

2. JSONP(JSON with Padding)

原理:利用<script>标签不受同源策略限制的特性

1
2
3
4
5
6
7
8
9
10
11
// 客户端
function handleResponse(data) {
console.log('Received:', data);
}

const script = document.createElement('script');
script.src = 'https://api.com/data?callback=handleResponse';
document.body.appendChild(script);

// 服务端响应
handleResponse({"name": "John", "age": 30});

局限性

  • 仅支持GET请求
  • 缺乏错误处理机制
  • 存在XSS风险

3. 反向代理

原理:同源请求代理服务器 → 代理转发到目标服务器

Nginx配置示例

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name web.com;

location /api/ {
proxy_pass https://api.com/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

4. WebSocket

1
2
3
4
5
6
7
8
9
10
// 客户端
const socket = new WebSocket('wss://api.com/ws');

socket.onopen = () => {
socket.send(JSON.stringify({action: 'subscribe'}));
};

socket.onmessage = (event) => {
console.log('Data:', JSON.parse(event.data));
};

5. postMessage

跨窗口通信

1
2
3
4
5
6
7
8
9
// 发送方(https://web.com)
const iframe = document.getElementById('other-site');
iframe.contentWindow.postMessage('Hello!', 'https://other.com');

// 接收方(https://other.com)
window.addEventListener('message', (event) => {
if (event.origin !== 'https://web.com') return;
console.log('Received:', event.data);
});

6. 现代浏览器API

跨域资源共享代理

1
2
3
// 使用CORS代理服务
fetch('https://cors-proxy.com/https://api.com/data')
.then(response => response.json())

浏览器支持

1
2
3
4
5
6
pie
title 跨域方案浏览器支持率
"CORS" : 98
"postMessage" : 97
"WebSocket" : 95
"JSONP" : 99

五、OPTIONS预检请求详解

1. 什么情况下触发OPTIONS请求?

  • 使用非简单请求方法(PUT/DELETE等)
  • 包含非标准请求头(自定义头)
  • Content-Type为application/json
  • 请求中带身份凭证(credentials)

2. OPTIONS请求示例

1
2
3
4
5
OPTIONS /api/user HTTP/1.1
Host: api.com
Origin: https://web.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-Header

3. 服务端响应示例

1
2
3
4
5
6
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://web.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true

4. 优化建议

  • 使用Access-Control-Max-Age减少预检请求
  • 合并多个自定义头减少预检次数
  • 尽可能使用简单请求

六、简单请求 vs 复杂请求

1. 简单请求条件

  1. 方法限制
    • GET
    • HEAD
    • POST
  2. 头限制
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(仅限于以下值):
      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded

简单请求流程

  1. 浏览器直接发送跨域请求(带Origin头)
  2. 服务器响应包含CORS头
  3. 浏览器验证响应头

2. 复杂请求条件

任何不符合简单请求条件的请求,包括:

  • PUT、DELETE、PATCH等方法
  • Content-Type为application/json
  • 包含自定义头(如Authorization)

复杂请求流程

  1. 浏览器发送OPTIONS预检请求
  2. 服务器响应预检请求
  3. 浏览器验证通过后发送实际请求
  4. 服务器响应实际请求

3. 对比总结

特性 简单请求 复杂请求
请求次数 1次 2次(预检+实际)
触发条件 符合特定方法/头限制 超出简单请求范围
性能影响 较大(多一次往返)
常见场景 表单提交、普通AJAX API请求、带认证请求
示例 fetch('https://api.com', { method: 'POST' }) fetch('https://api.com', { method: 'PUT', headers: {'Authorization': 'Bearer ...'} })

七、表单提交与跨域

1. 表单提交的特性

1
2
3
4
<form action="https://other.com/api" method="POST">
<input type="text" name="username">
<button type="submit">提交</button>
</form>

关键特性

  • 允许跨域提交:浏览器不会阻止表单的跨域提交
  • 🔄 页面跳转:表单提交后浏览器会导航到目标URL
  • 响应受限:提交后浏览器会离开当前页面,无法直接处理跨域响应

2. AJAX表单提交的跨域问题

1
2
3
4
5
6
7
8
9
10
// 使用AJAX提交表单
const formData = new FormData(document.getElementById('myForm'));

fetch('https://other.com/api', {
method: 'POST',
body: formData
}).then(response => {
// 这里会触发跨域检查
console.log(response);
});

结果

  • 需要服务端配置CORS响应头
  • 否则浏览器会阻止JavaScript读取响应

3. 解决方案

  1. 传统表单:接受页面跳转
  2. AJAX提交
    • 服务端配置CORS
    • 使用代理服务器中转
  3. 隐藏iframe技巧
    1
    2
    3
    4
    <form target="hiddenFrame" action="https://other.com/api" method="POST">
    <!-- 表单内容 -->
    </form>
    <iframe name="hiddenFrame" style="display:none"></iframe>

八、安全最佳实践

  1. 精确配置CORS

    1
    2
    // 避免使用通配符*
    res.setHeader('Access-Control-Allow-Origin', 'https://trusted-domain.com');
  2. 凭证控制

    1
    2
    3
    4
    5
    // 前端
    fetch(url, { credentials: 'include' });

    // 后端
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  3. CSRF防护

    • 使用SameSite Cookie属性
    • 实现CSRF令牌机制
    • 验证Origin/Referer头
  4. 避免JSONP安全风险

    • 严格验证回调函数名
    • 实施内容安全策略(CSP)
    • 优先使用CORS替代JSONP

九、现代跨域技术趋势

  1. 跨域隔离(Cross-Origin Isolation)

    • 使用COOP(Cross-Origin Opener Policy)
    • 使用COEP(Cross-Origin Embedder Policy)
      1
      2
      Cross-Origin-Opener-Policy: same-origin
      Cross-Origin-Embedder-Policy: require-corp
  2. SharedArrayBuffer

    • 需要跨域隔离环境
    • 启用高性能并行计算
  3. 联邦学习(Federated Learning)

    • 跨域数据协作新范式
    • 隐私保护下的模型训练
  4. Web容器技术

    • 微前端架构
    • 模块联邦(Module Federation)

总结

跨域是现代Web开发中的核心问题,理解其原理和解决方案至关重要:

  1. 同源策略是浏览器安全基石,限制跨域脚本交互
  2. CORS是主流解决方案,需前后端协作实现
  3. 简单请求直接发送,复杂请求需要预检
  4. 表单提交允许跨域但导致页面跳转
  5. OPTIONS预检是复杂请求的必要步骤
  6. 多种替代方案适用于不同场景(JSONP/代理/WebSocket等)

随着Web生态发展,跨域技术也在不断演进。开发人员应掌握核心原理,根据实际需求选择适当方案,同时关注新兴的跨域安全模型和技术趋势。