HTTP 跨域问题 CORS 配置详解

FreeGuideOnline 9阅读 2026-07-11

HTTP 跨域问题与 CORS 配置完全指南

为什么会有跨域问题?

浏览器的同源策略(Same-Origin Policy) 是 Web 安全的基石。它限制了一个源的文档或脚本如何与另一个源的资源进行交互。同源要求协议、域名(主机名)和端口号三者完全相同。

页面源 请求资源源 是否跨域 原因
https://www.example.com https://www.example.com/api 协议、域名、端口一致
http://www.example.com https://www.example.com 协议不同
https://example.com https://api.example.com 子域名不同
https://example.com:443 https://example.com:8443 端口不同

当浏览器发现脚本发起的请求跨越了源,就会拦截服务器的响应,并抛出经典的 Access to XMLHttpRequest at '...' from origin '...' has been blocked by CORS policy 错误。这种拦截发生在浏览器端,服务器实际上可能已经处理并响应了请求。


CORS 是什么?

CORS(Cross-Origin Resource Sharing,跨源资源共享) 是一种基于 HTTP 头的机制,它允许服务器声明哪些外部源有权访问其资源。通过一组特殊的请求头与响应头,浏览器与服务器进行“协商”,决定是否放行跨域请求。

CORS 将跨域请求分为两类:

  • 简单请求(Simple Request)
  • 非简单请求(Preflighted Request)

简单请求(Simple Request)

若一个请求同时满足以下所有条件,即为简单请求:

  1. 使用的方法为 GETHEADPOST 之一。
  2. 除了浏览器自动设置的标头(如 ConnectionUser-Agent),只包含 CORS 安全列出的标头:AcceptAccept-LanguageContent-LanguageContent-Type
  3. Content-Type 的值仅限于 application/x-www-form-urlencodedmultipart/form-datatext/plain
  4. 请求中的任何 XMLHttpRequestUpload 对象均未注册事件监听器。
  5. 请求中未使用 ReadableStream 对象。

对于简单请求,浏览器会直接在请求头中附加 Origin 字段,标明当前页面的源。服务器检查后,在响应中返回 Access-Control-Allow-Origin 头。

简单请求完整交互示例:

GET /data HTTP/1.1
Host: api.example.com
Origin: https://www.example.com

服务器响应:

HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: https://www.example.com

Access-Control-Allow-Origin 的值与 Origin 匹配,或为通配符 *(对于不携带凭据的请求),则浏览器允许前端脚本读取响应。


非简单请求与预检(Preflight)

不满足简单请求条件的请求(例如使用了 PUTDELETE 方法,或 Content-Type: application/json),浏览器会先发送一个 预检请求(Preflight Request),使用 OPTIONS 方法询问服务器是否允许实际请求。

预检请求携带的关键头:

  • Origin:请求来源。
  • Access-Control-Request-Method:实际请求将使用的 HTTP 方法。
  • Access-Control-Request-Headers:实际请求将携带的自定义头部列表。

以下是一个非简单请求的完整流程:

1. 预检请求:

OPTIONS /api/update HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header, Content-Type

2. 服务器预检响应:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: PUT, DELETE, PATCH
Access-Control-Allow-Headers: X-Custom-Header, Content-Type
Access-Control-Max-Age: 86400
  • Access-Control-Allow-Methods:明确列出允许的方法。
  • Access-Control-Allow-Headers:明确列出允许的请求头。
  • Access-Control-Max-Age:可选,表示预检响应的缓存时间(秒),在此时间内相同请求不再需要预检。

3. 预检成功后,发送实际请求:

PUT /api/update HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Content-Type: application/json
X-Custom-Header: value

4. 实际响应仍必须包含 CORS 头:

HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: https://www.example.com

携带凭据的跨域请求

默认情况下,跨域请求不发送 Cookie 和 HTTP 认证信息。若需要携带凭据,需要:

  1. 前端:设置 XMLHttpRequestwithCredentials = true,或使用 fetchcredentials: 'include'
  2. 服务器
    • Access-Control-Allow-Origin 不能*,必须指定具体源。
    • 响应头必须包含 Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true

若缺少 Access-Control-Allow-Credentials: true,浏览器同样会拦截响应。


常见的 CORS 响应头一览

响应头 说明
Access-Control-Allow-Origin 指定允许访问资源的源,只能设置一个源,或 *(不能与凭据同用)
Access-Control-Allow-Methods 预检响应中指定允许的 HTTP 方法,如 GET, POST, PUT
Access-Control-Allow-Headers 预检响应中指定允许的请求头,如 X-Custom-Header
Access-Control-Expose-Headers 允许前端 JavaScript 访问的响应头(除了 Cache-Control 等基本头)
Access-Control-Allow-Credentials 是否允许发送凭据(Cookie),值为 true
Access-Control-Max-Age 预检请求缓存秒数,默认 5 秒

服务器端 CORS 配置实战

1. Nginx 配置

在 Nginx 中,通常使用 add_header 指令在 location 块中添加 CORS 头。推荐为预检请求单独处理或直接添加到需要的接口路径。

location /api/ {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' 'https://www.example.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With';
        add_header 'Access-Control-Allow-Credentials' 'true';
        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://www.example.com';
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    # 其余代理或静态文件配置...
}

2. Apache 配置

.htaccess 或虚拟主机配置中启用 mod_headers

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://www.example.com"
    Header set Access-Control-Allow-Credentials "true"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization"
    Header set Access-Control-Max-Age "1728000"
</IfModule>

RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]

3. Node.js (Express) 配置

使用 cors 中间件是最简单的办法,也可手动设置:

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

// 允许特定源,携带凭据
const corsOptions = {
    origin: 'https://www.example.com',
    credentials: true,
    optionsSuccessStatus: 200
};

app.use(cors(corsOptions));

// 或手动设置
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', 'https://www.example.com');
    res.header('Access-Control-Allow-Credentials', 'true');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    if (req.method === 'OPTIONS') {
        return res.sendStatus(204);
    }
    next();
});

4. Java (Spring Boot) 配置

全局配置 CORS 最简单的方式是添加 WebMvcConfigurer

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("https://www.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

5. Python (Flask) 配置

使用 flask-cors 扩展或手动添加 after_request 钩子:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app, origins="https://www.example.com", supports_credentials=True)

常见跨域错误排查

错误信息 原因分析及解决方案
No 'Access-Control-Allow-Origin' header is present 服务器未返回 CORS 头。检查服务端配置,确保对 OPTIONS 请求也返回正确头。
The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include' 使用了 withCredentials 但服务器返回 Access-Control-Allow-Origin: *,改指定具体源并添加 Access-Control-Allow-Credentials: true
Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers 预检检查失败,需在 Access-Control-Allow-Headers 中明确列出该头。
Method PUT is not allowed by Access-Control-Allow-Methods 预检检查失败,确保 Access-Control-Allow-Methods 中包含了实际请求方法。
预检请求返回 404 或 500 服务器未正确处理 OPTIONS 请求,需要配置路由或框架全局处理 OPTIONS。

安全注意事项

  • 严格控制允许的源:不要直接将 Origin 反射回 Access-Control-Allow-Origin,否则可能为恶意站点放行。
  • 避免滥用通配符 *:一旦应用携带凭据,则不可使用 *。即使不携带凭据,明确列出可信源也是最佳实践。
  • 限制 Access-Control-Allow-Headers:不要使用 * 通配符,明确列出必须的自定义头,减少攻击面。
  • 保护预检缓存:合理设置 Access-Control-Max-Age 以减少预检请求频次,但不宜过长,以免权限变更后长期生效。

总结

CORS 是浏览器强制执行的同源策略的“协商解除”机制。正确理解简单请求与预检请求,并在服务器端准确配置相应头部,是解决前端跨域问题的根本方案。无论使用何种后端技术,核心都是让服务器在需要的响应中返回正确的 Access-Control-Allow-* 头,并正确处理 OPTIONS 预检请求。