8.5 CORS 与跨域请求

8.5 CORS 与跨域请求

跨域资源共享(CORS)是现代Web开发中处理跨域请求的标准机制,它通过特定的HTTP头部允许服务器声明哪些外部源可以访问其资源。本节将深入解析CORS的工作原理、实现方式和最佳实践。

核心机制与流程

1. 同源策略基础

同源策略要求"协议+域名+端口"完全一致才允许直接通信。以下是典型同源检测结果示例:

请求URL 当前源 是否同源 原因
https://example.com/api https://example.com 完全一致
http://example.com/api https://example.com 协议不同
https://api.example.com https://example.com 子域名不同
https://example.com:8080 https://example.com 端口不同

2. CORS工作流程

简单请求流程

sequenceDiagram
    Client->>Server: GET /resource
    Server-->>Client: Access-Control-Allow-Origin: *
    Client->>Client: 允许响应数据

**预检请求流程**:
sequenceDiagram
    Client->>Server: OPTIONS /resource
    Server-->>Client: Access-Control-Allow-Origin: https://example.com
    Server-->>Client: Access-Control-Allow-Methods: PUT, DELETE
    Client->>Server: PUT /resource
    Server-->>Client: Access-Control-Allow-Origin: https://example.com

服务器端配置详解

1. Node.js Express配置

const express = require('express');
const cors = require('cors');

const app = express();

// 基本CORS配置(允许所有源)
app.use(cors());

// 高级配置
const corsOptions = {
  origin: [
    'https://example.com',
    'https://dev.example.com',
    'http://localhost:3000'
  ],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-Custom-Header'],
  credentials: true,
  maxAge: 86400,
  preflightContinue: false,
  optionsSuccessStatus: 204
};

app.use('/api', cors(corsOptions), apiRouter);

// 动态origin回调
const dynamicCorsOptions = {
  origin: function (origin, callback) {
    if (!origin || whitelist.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
};

2. Nginx反向代理配置

server {
    listen 80;
    server_name api.example.com;

    location / {
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'https://example.com';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,Content-Type';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        add_header 'Access-Control-Allow-Origin' 'https://example.com';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';

        proxy_pass http://backend;
    }
}

客户端处理方案

1. Fetch API使用规范

// 基础跨域请求
fetch('https://api.example.com/data', {
  method: 'GET',
  mode: 'cors', // 默认值,可省略
  headers: {
    'Content-Type': 'application/json'
  },
  credentials: 'include' // 需要携带cookie时
})
.then(response => {
  if (!response.ok) throw new Error('Network error');
  return response.json();
})
.catch(error => {
  console.error('请求失败:', error);
});

// 预检请求示例
fetch('https://api.example.com/data', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'
  },
  body: JSON.stringify({ key: 'value' })
});

2. 错误处理与调试

async function safeFetch(url, options = {}) {
  try {
    const response = await fetch(url, {
      ...options,
      mode: 'cors'
    });
    
    // 检查CORS头
    if (!response.headers.get('access-control-allow-origin')) {
      throw new Error('Missing CORS headers');
    }
    
    // 处理不同响应类型
    const contentType = response.headers.get('content-type');
    if (contentType.includes('application/json')) {
      return await response.json();
    }
    return await response.text();
  } catch (error) {
    console.error('CORS请求失败:', {
      url,
      error: error.message,
      type: error.name
    });
    
    // 回退方案
    return fallbackRequest(url);
  }
}

// 使用示例
safeFetch('https://api.example.com/data')
  .then(data => console.log('响应数据:', data));

高级场景处理

1. 携带凭据的请求

// 服务器配置
const corsOptions = {
  origin: 'https://example.com',
  credentials: true,
  allowedHeaders: ['Content-Type', 'Authorization']
};

// 客户端请求
fetch('https://api.example.com/user', {
  credentials: 'include',
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

2. 自定义头部处理

// 预检请求需要的配置
app.options('/special', cors({
  allowedHeaders: ['X-Custom-Header', 'X-Extra-Header'],
  methods: ['POST']
}));

app.post('/special', cors(), (req, res) => {
  res.json({ status: 'ok' });
});

// 客户端发送自定义头
fetch('https://api.example.com/special', {
  method: 'POST',
  headers: {
    'X-Custom-Header': 'value',
    'Content-Type': 'application/json'
  }
});

安全最佳实践

1. 生产环境配置建议

// 严格的白名单控制
const whitelist = [
  'https://example.com',
  'https://www.example.com',
  'https://staging.example.com'
];

const corsOptionsDelegate = function (req, callback) {
  let corsOptions;
  const origin = req.header('Origin');
  
  if (whitelist.indexOf(origin) !== -1) {
    corsOptions = { 
      origin: true,
      methods: ['GET', 'POST'],
      allowedHeaders: ['Content-Type'],
      maxAge: 600
    };
  } else {
    corsOptions = { origin: false };
  }
  callback(null, corsOptions);
};

app.use(cors(corsOptionsDelegate));

2. 常见漏洞防护

反射型CORS滥用防护

// 不安全的配置示例(避免!)
app.use(cors({
  origin: true, // 反射请求的Origin头
  credentials: true
}));

// 安全替代方案
app.use(cors({
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      // 非白名单源仍然返回200但不带CORS头
      callback(null, false);
    }
  }
}));

性能优化策略

1. 预检缓存优化

# 服务器响应头示例
Access-Control-Max-Age: 86400  # 缓存1天(秒)
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type

2. CDN层CORS配置

# AWS CloudFront行为配置示例
Response headers policy:
  - Access-Control-Allow-Origin: https://example.com
  - Access-Control-Allow-Methods: GET, HEAD, OPTIONS
  - Access-Control-Max-Age: 600
  - Access-Control-Expose-Headers: Date

调试工具与技巧

1. Chrome开发者工具

  • Network面板:查看CORS相关请求头/响应头
  • Console警告:识别CORS策略违规
  • 命令行检测
    // 检查CORS配置
    fetch('https://api.example.com', { method: 'OPTIONS' })
      .then(res => console.log(res.headers));
    

2. 常见错误解析

错误信息 原因分析 解决方案
No 'Access-Control-Allow-Origin' 服务器未返回CORS头 检查服务器配置
Credentials not supported 服务器未设置allow-credentials 服务器添加allow-credentials: true
Method not allowed 预检请求未包含该方法 更新allow-methods
Header not allowed 请求包含未允许的头部 扩展allow-headers或简化请求

替代方案比较

方案 优点 缺点 适用场景
CORS 标准、安全、灵活 需要服务器配合 可控的跨域API调用
JSONP 兼容老旧浏览器 仅GET、安全性差 遗留系统兼容
代理服务器 完全避免跨域问题 额外性能开销 无法修改API服务时
WebSocket 双向通信、低延迟 协议不同、复杂度高 实时应用

实际应用案例

1. 跨域AJAX上传

// 客户端代码
const uploadFile = (file) => {
  const formData = new FormData();
  formData.append('file', file);
  
  return fetch('https://storage.example.com/upload', {
    method: 'POST',
    body: formData,
    headers: {
      'X-Auth-Token': 'secret123'
    },
    credentials: 'include'
  });
};

// 服务器配置
app.post('/upload', cors({
  origin: 'https://app.example.com',
  allowedHeaders: ['X-Auth-Token', 'Content-Type'],
  credentials: true
}), uploadHandler);

2. 多域名SSO集成

// 身份验证服务配置
const ssoCorsOptions = {
  origin: [
    'https://portal.example.com',
    'https://app.example.com',
    'https://internal.example.com'
  ],
  methods: ['GET', 'POST'],
  allowedHeaders: ['Content-Type', 'X-Requested-With'],
  credentials: true,
  maxAge: 3600
};

app.use('/auth', cors(ssoCorsOptions), authRouter);

// 客户端检查
if (crossOriginIsolated) {
  // 使用SharedArrayBuffer等高级特性
} else {
  console.warn('页面未处于跨域隔离状态');
}

CORS作为现代Web开发的核心安全机制,正确实施需要注意:

  • 生产环境避免使用*通配符
  • 严格限制allow-credentials的使用范围
  • 为敏感操作设置合理的max-age
  • 结合内容安全策略(CSP)提供纵深防御
  • 定期审计CORS配置与使用情况

通过合理配置CORS策略,开发者可以在保障安全的前提下,构建功能丰富的跨源Web应用。

#前端开发 分享于 2025-05-20

【 内容由 AI 共享,不代表本站观点,请谨慎参考 】