1. 跨域请求的本质与安全边界
浏览器同源策略(Same-Origin Policy)是跨域问题的根源所在。这个策略规定:只有当协议、域名、端口三者完全相同时才属于同源。例如:
https://example.com/app和https://api.example.com/data属于跨域(二级域名不同)http://localhost:3000和http://localhost:8080属于跨域(端口不同)
同源策略限制的具体行为包括:
- AJAX请求默认被拦截
- DOM访问受限(如iframe跨域通信)
- Cookie/LocalStorage读取限制
注意:跨域限制是浏览器行为,服务端之间的通信不受此限制。这就是为什么Postman能直接访问接口而浏览器会报错。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 常见跨域解决方案技术对比
2.1 CORS机制详解
CORS(跨域资源共享)是W3C标准方案,通过HTTP头实现控制。服务端配置示例(Node.js):
javascript复制app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://yourdomain.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') // 允许携带cookie
next()
})
关键头字段说明:
Access-Control-Allow-Origin: 允许的源Access-Control-Max-Age: 预检请求缓存时间Access-Control-Expose-Headers: 允许客户端访问的响应头
2.2 反向代理方案
开发环境常用webpack-dev-server配置:
javascript复制devServer: {
proxy: {
'/api': {
target: 'http://backend:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
生产环境Nginx配置示例:
nginx复制location /api/ {
proxy_pass http://backend-server/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
2.3 JSONP的局限性
仅支持GET请求的临时方案:
javascript复制function handleResponse(data) {
console.log('Received:', data)
}
const script = document.createElement('script')
script.src = 'https://api.example.com/data?callback=handleResponse'
document.body.appendChild(script)
3. 预检请求(Preflight)全流程解析
复杂请求(如Content-Type为application/json的POST)会触发预检。完整流程:
- 浏览器发送OPTIONS请求:
http复制OPTIONS /resource HTTP/1.1
Origin: https://yourdomain.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
- 服务端响应必须包含:
http复制HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yourdomain.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
- 通过预检后才会发送实际请求
4. 实战中的高频问题排查
4.1 证书导致的跨域问题
混合内容(HTTPS页面请求HTTP接口)会被浏览器拦截。解决方案:
- 全站HTTPS化
- 配置HSTS头
- 使用相对协议
//api.example.com
4.2 带Cookie的跨域请求
必须满足三个条件:
- 服务端设置
Access-Control-Allow-Credentials: true - 客户端设置
withCredentials: true Access-Control-Allow-Origin不能为*,必须指定具体域名
Axios示例:
javascript复制axios.get('https://api.example.com/data', {
withCredentials: true
})
4.3 缓存引发的跨域异常
某些浏览器会缓存CORS头,导致配置更新后依然报错。解决方案:
- 开发阶段禁用缓存(Chrome开发者工具Network勾选Disable cache)
- 生产环境为静态资源添加hash版本号
5. 现代前端框架的跨域处理
5.1 Vue CLI的代理配置
vue.config.js示例:
javascript复制module.exports = {
devServer: {
proxy: {
'^/api': {
target: 'http://localhost:8080',
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': '/api/v1'
}
}
}
}
}
5.2 Create React App解决方案
通过setupProxy.js配置:
javascript复制const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
)
}
6. 服务端渲染(SSR)的特殊处理
当使用Next.js/Nuxt.js等SSR框架时:
- 服务端请求无需处理跨域(直接Node.js发起)
- 客户端请求仍需CORS
- 推荐方案:API路由统一前缀,通过环境变量区分:
javascript复制const API_BASE = process.server
? 'http://backend-service'
: '/api'
export default {
async getData() {
return await $axios.$get(`${API_BASE}/endpoint`)
}
}
7. WebSocket的跨域注意事项
WS协议同样受同源策略限制。服务端需要:
javascript复制const wss = new WebSocket.Server({
verifyClient: (info, done) => {
const origin = info.origin
if(allowedOrigins.includes(origin)) {
return done(true)
}
done(false, 401, 'Unauthorized')
}
})
客户端连接时需验证:
javascript复制const socket = new WebSocket('wss://api.example.com')
socket.onerror = (err) => {
console.error('Connection error:', err)
}
8. 移动端混合开发的特殊场景
8.1 Cordova/PhoneGap应用
需要在config.xml中配置白名单:
xml复制<allow-navigation href="*" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
8.2 React Native解决方案
iOS需要配置Info.plist:
xml复制<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Android则在AndroidManifest.xml添加:
xml复制<application
android:usesCleartextTraffic="true">
</application>
9. 测试策略与自动化验证
9.1 Jest单元测试模拟
javascript复制global.fetch = jest.fn(() =>
Promise.resolve({
headers: new Headers({
'Access-Control-Allow-Origin': '*'
}),
json: () => Promise.resolve({ data: 'mock' })
})
)
test('should handle CORS response', async () => {
const res = await fetch('https://api.example.com')
expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*')
})
9.2 Cypress端到端测试
cypress.json配置:
json复制{
"chromeWebSecurity": false
}
测试脚本示例:
javascript复制describe('CORS Test', () => {
it('should make cross-origin request', () => {
cy.request({
url: 'https://api.example.com/data',
method: 'GET',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(200)
expect(response.headers).to.have.property('access-control-allow-origin')
})
})
})
10. 安全加固最佳实践
-
生产环境严格限制
Access-Control-Allow-Origin:- 动态匹配允许的域名列表
- 拒绝来源为
null的请求
-
敏感操作增加CSRF防护:
- 同步器令牌模式
- 双重Cookie验证
-
定期审计CORS配置:
bash复制# 使用curl测试 curl -H "Origin: http://malicious.com" -I https://api.example.com -
监控异常跨域请求:
- 检查
Origin头是否在白名单 - 记录非常规的
Referer模式
- 检查
