HTTP 跨域问题 CORS 配置详解
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)
若一个请求同时满足以下所有条件,即为简单请求:
- 使用的方法为
GET、HEAD、POST之一。 - 除了浏览器自动设置的标头(如
Connection、User-Agent),只包含 CORS 安全列出的标头:Accept、Accept-Language、Content-Language、Content-Type。 Content-Type的值仅限于application/x-www-form-urlencoded、multipart/form-data、text/plain。- 请求中的任何
XMLHttpRequestUpload对象均未注册事件监听器。 - 请求中未使用
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)
不满足简单请求条件的请求(例如使用了 PUT、DELETE 方法,或 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 认证信息。若需要携带凭据,需要:
- 前端:设置
XMLHttpRequest的withCredentials = true,或使用fetch的credentials: 'include'。 - 服务器:
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 预检请求。