跨域请求处理
跨域请求的基本概念
跨域请求是指浏览器出于安全考虑,限制从一个源加载的脚本与另一个源的资源进行交互。同源策略要求协议、域名和端口完全相同才算同源。当这三个要素中任意一个不同时,就会产生跨域问题。例如:
http://a.com
请求https://a.com
(协议不同)http://a.com
请求http://b.com
(域名不同)http://a.com:80
请求http://a.com:8080
(端口不同)
常见的跨域解决方案
JSONP
JSONP利用<script>
标签不受同源策略限制的特性实现跨域请求。它通过动态创建script标签,将回调函数名作为参数传递给服务器,服务器返回数据时调用该函数。
function handleResponse(data) {
console.log('Received data:', data);
}
const script = document.createElement('script');
script.src = 'http://example.com/api?callback=handleResponse';
document.body.appendChild(script);
CORS(跨域资源共享)
CORS是现代浏览器支持的标准跨域解决方案。服务器通过设置响应头来声明允许哪些源访问资源。
// 服务器端设置
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
代理服务器
通过同源的后端服务器转发请求,绕过浏览器的同源策略限制。
// 前端请求同源服务器
fetch('/api/proxy', {
method: 'POST',
body: JSON.stringify({
url: 'http://target-domain.com/api'
})
})
// 后端Node.js示例
app.post('/api/proxy', async (req, res) => {
const { url } = req.body;
const response = await axios.get(url);
res.json(response.data);
});
复杂请求的预检机制
对于可能对服务器数据产生副作用的HTTP请求方法(如PUT、DELETE等),浏览器会先发送OPTIONS请求进行预检。
// 预检请求示例
fetch('http://api.example.com/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
},
body: JSON.stringify({ key: 'value' })
});
服务器需要正确处理OPTIONS请求:
app.options('/data', (req, res) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'PUT');
res.header('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header');
res.sendStatus(204);
});
跨域请求中的凭证问题
默认情况下,跨域请求不会发送cookie等凭证信息。需要特殊设置:
// 前端设置
fetch('http://api.example.com/data', {
credentials: 'include'
});
// 服务器端设置
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Origin', 'http://your-domain.com'); // 不能是*
WebSocket跨域处理
WebSocket协议本身支持跨域,但服务器可以决定是否接受来自特定源的连接。
const socket = new WebSocket('ws://example.com/socket');
// 服务器端(Node.js + ws库)
const WebSocket = require('ws');
const wss = new WebSocket.Server({
verifyClient: (info, done) => {
const origin = info.origin;
if (origin === 'http://allowed-domain.com') {
done(true);
} else {
done(false, 401, 'Unauthorized');
}
}
});
跨域图片资源处理
Canvas操作跨域图片时需要特殊处理:
const img = new Image();
img.crossOrigin = 'Anonymous'; // 请求CORS权限
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 现在可以安全地操作canvas了
};
img.src = 'http://example.com/image.jpg';
跨域字体加载
Web字体文件也受同源策略限制,需要在服务器设置CORS头:
@font-face {
font-family: 'MyFont';
src: url('http://example.com/font.woff') format('woff');
font-display: swap;
}
服务器响应需要包含:
Access-Control-Allow-Origin: *
跨域AJAX请求的异常处理
跨域请求失败时,浏览器提供的错误信息有限,需要特别注意错误处理:
fetch('http://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);
// 注意:跨域请求无法获取详细的错误信息
});
本地开发环境跨域解决方案
开发时常用的解决方案:
-
禁用浏览器安全策略(仅限开发)
google-chrome --disable-web-security --user-data-dir=/tmp/chrome
-
使用开发服务器代理
// vite.config.js export default { server: { proxy: { '/api': { target: 'http://api.example.com', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } } }
跨域请求的性能优化
-
预加载CORS资源:
<link rel="preconnect" href="https://api.example.com"> <link rel="preload" href="https://api.example.com/data.json" as="fetch" crossorigin>
-
合并请求减少预检次数
-
缓存CORS预检结果:
Access-Control-Max-Age: 86400
特殊场景下的跨域处理
postMessage跨文档通信
// 发送方
const popup = window.open('http://other-domain.com');
popup.postMessage('Hello', 'http://other-domain.com');
// 接收方
window.addEventListener('message', event => {
if (event.origin !== 'http://your-domain.com') return;
console.log('Received message:', event.data);
});
跨域iframe通信
// 父页面
document.getElementById('iframe').contentWindow.postMessage('Hello', 'http://child-domain.com');
// iframe内
window.parent.postMessage('Response', 'http://parent-domain.com');
浏览器存储的跨域限制
- localStorage和IndexedDB遵循同源策略
- 共享Worker可以有限制地跨域使用
- Service Worker的作用域限制
// 注册Service Worker时明确作用域
navigator.serviceWorker.register('/sw.js', {
scope: '/app/' // 只能控制/app/路径下的请求
});
现代前端框架的跨域处理
React中的代理设置
// setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://api.example.com',
changeOrigin: true,
})
);
};
Vue CLI的跨域配置
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com',
ws: true,
changeOrigin: true
}
}
}
}
跨域请求的安全注意事项
- 谨慎设置
Access-Control-Allow-Origin: *
- 验证所有跨域请求的来源
- 限制允许的HTTP方法
- 对敏感操作实施CSRF保护
// 检查Origin头的示例中间件
app.use((req, res, next) => {
const allowedOrigins = ['http://site1.com', 'http://site2.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
}
next();
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn