您现在的位置是:网站首页 > 跨域请求处理文章详情

跨域请求处理

跨域请求的基本概念

浏览器出于安全考虑,采用同源策略限制不同源的资源交互。同源指协议、域名和端口完全相同。跨域请求发生在客户端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类型提供具体处理
  })

上一篇: JSONP原理

下一篇: 异步错误处理

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步