您现在的位置是:网站首页 > 跨域请求处理文章详情
跨域请求处理
陈川
【
JavaScript
】
56266人已围观
5708字
跨域请求的基本概念
浏览器出于安全考虑,采用同源策略限制不同源的资源交互。同源指协议、域名和端口完全相同。跨域请求发生在客户端JavaScript尝试访问不同源的资源时。现代Web应用常需要从多个域获取数据,理解跨域机制对开发者至关重要。
// 同源示例
// 当前页面URL: https://example.com/index.html
fetch('https://example.com/api/data') // 同源请求
// 跨域示例
fetch('https://api.other.com/data') // 跨域请求
CORS机制详解
跨源资源共享(CORS)是W3C标准,允许服务器声明哪些外部源可以访问资源。浏览器自动处理CORS流程,开发者需要配置服务器响应头。
关键响应头字段:
Access-Control-Allow-Origin
: 指定允许访问的源Access-Control-Allow-Methods
: 允许的HTTP方法Access-Control-Allow-Headers
: 允许的请求头Access-Control-Allow-Credentials
: 是否允许发送凭证
// Express服务器CORS配置示例
const express = require('express')
const app = express()
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://client.example.com')
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.header('Access-Control-Allow-Credentials', 'true')
next()
})
预检请求处理
对于非简单请求,浏览器会先发送OPTIONS方法的预检请求。服务器需要正确处理这类请求。
触发预检请求的条件:
- 使用PUT、DELETE等非简单方法
- 自定义请求头
- Content-Type非application/x-www-form-urlencoded、multipart/form-data或text/plain
// 会触发预检请求的示例
fetch('https://api.example.com/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
},
body: JSON.stringify({ key: 'value' })
})
JSONP跨域方案
在CORS出现前,JSONP是常用的跨域解决方案。它利用script标签不受同源策略限制的特性。
function handleResponse(data) {
console.log('Received data:', data)
}
// 动态创建script标签
const script = document.createElement('script')
script.src = 'https://api.example.com/data?callback=handleResponse'
document.body.appendChild(script)
// 服务器应返回类似: handleResponse({...data...})
代理服务器方案
当无法修改目标服务器CORS配置时,可通过同源代理服务器转发请求。
// 前端调用同源代理接口
fetch('/api/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://target-api.com/endpoint',
method: 'GET'
})
})
// Node.js代理服务器示例
const axios = require('axios')
app.post('/api/proxy', async (req, res) => {
try {
const { url, method, body } = req.body
const response = await axios({
method,
url,
data: body
})
res.json(response.data)
} catch (error) {
res.status(500).json({ error: error.message })
}
})
WebSocket跨域通信
WebSocket协议不受同源策略限制,但服务器仍需验证Origin头。
const socket = new WebSocket('wss://echo.websocket.org')
socket.onopen = function() {
console.log('Connection established')
socket.send('Hello Server!')
}
socket.onmessage = function(event) {
console.log('Message from server:', event.data)
}
跨域资源共享的实际问题
凭证携带: 需要同时设置客户端withCredentials和服务器Access-Control-Allow-Credentials
fetch('https://api.example.com/secure', {
credentials: 'include'
})
// 服务器响应头必须包含
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://client.example.com // 不能使用*
缓存预检请求: Access-Control-Max-Age控制预检请求缓存时间
Access-Control-Max-Age: 86400 // 24小时
现代浏览器的跨域限制
常见限制场景:
- 本地文件协议(file://)发起的请求
- 跨域iframe通信
- 字体、Web Worker等特殊资源的加载
// 跨域iframe通信示例
// 父页面
window.addEventListener('message', (event) => {
if (event.origin !== 'https://child.example.com') return
console.log('Message from child:', event.data)
})
// 子iframe
parent.postMessage('Hello parent!', 'https://parent.example.com')
服务端代理的进阶实现
对于生产环境,可使用Nginx反向代理处理跨域:
location /api/ {
proxy_pass https://target-api.com/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Credentials true;
}
浏览器扩展的跨域处理
Chrome扩展可通过manifest声明跨域权限:
{
"permissions": [
"https://*.example.com/*",
"crossOriginIsolated"
]
}
性能优化考虑
预检请求优化:
- 合并API端点减少预检次数
- 合理设置Access-Control-Max-Age
- 避免不必要的自定义头
缓存策略:
- 对GET请求实施缓存
- 使用ETag或Last-Modified头
// 带缓存的跨域请求示例
fetch('https://api.example.com/data', {
headers: {
'If-None-Match': 'previous-etag-value'
}
})
安全最佳实践
重要安全措施:
- 严格限制Access-Control-Allow-Origin
- 验证Origin头防止CSRF攻击
- 敏感操作要求预检请求
- 避免通配符(*)与凭证同时使用
// 服务器端Origin验证示例
app.use((req, res, next) => {
const allowedOrigins = [
'https://client1.example.com',
'https://client2.example.com'
]
const origin = req.headers.origin
if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin)
res.header('Vary', 'Origin')
}
next()
})
特殊场景处理
跨域重定向问题: 浏览器会跟随重定向但可能丢失CORS头
// 禁止自动跟随重定向
fetch('https://api.example.com/redirect', {
redirect: 'manual'
}).then(response => {
if (response.type === 'opaqueredirect') {
// 处理重定向
}
})
二进制数据传输: 处理跨域二进制数据需要特殊设置
fetch('https://api.example.com/image', {
headers: {
'Accept': 'image/webp,image/apng,image/*,*/*'
}
})
.then(response => response.blob())
.then(blob => {
const img = new Image()
img.src = URL.createObjectURL(blob)
document.body.appendChild(img)
})
调试技巧
Chrome开发者工具:
- 网络面板查看CORS相关请求头
- 控制台错误信息分析
- 禁用安全标志调试(仅开发环境)
# Chrome启动参数禁用安全限制(仅测试用)
google-chrome --disable-web-security --user-data-dir=/tmp/chrome-test
常见错误处理:
- 预检请求失败检查服务器OPTIONS处理
- 凭证错误检查withCredentials配置
- 响应头缺失验证服务器中间件
// 错误处理示例
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
})
.catch(error => {
console.error('Fetch error:', error)
// 根据error类型提供具体处理
})