1. 跨域访问的本质与CORS机制解析
当我们在浏览器地址栏输入https://example.com访问某个网站时,这个网站的前端代码可能会通过JavaScript请求https://api.example.com的接口数据。虽然这两个域名看起来相似,但在浏览器看来却是完全不同的"源"(Origin)。这种不同源之间的请求,就是典型的跨域访问场景。
现代浏览器基于安全考虑,默认禁止这类跨域请求。这就是为什么我们经常在浏览器控制台看到这样的错误提示:
code复制Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'https://example.com' has been blocked by CORS policy
CORS(Cross-Origin Resource Sharing)机制就是为了解决这个问题而设计的。它通过在HTTP头信息中添加特定字段,让服务器明确告诉浏览器:"我允许哪些来源的网站访问我的资源"。
关键点:CORS是一种"选择性放行"机制,而不是完全开放。服务器始终掌握着控制权,可以精确指定允许哪些外部域访问自己的资源。
2. CORS的核心工作原理
2.1 简单请求与非简单请求
浏览器将跨域请求分为两类:
-
简单请求:满足以下所有条件:
- 使用GET、HEAD或POST方法
- 仅包含安全的头部字段(如Accept、Accept-Language等)
- Content-Type为text/plain、multipart/form-data或application/x-www-form-urlencoded
-
非简单请求:不满足上述任一条件的请求,如PUT/DELETE方法、自定义头部、application/json内容类型等
对于简单请求,浏览器会直接发出请求,并在请求头中添加Origin字段。服务器需要响应Access-Control-Allow-Origin头部来表明是否允许该来源。
对于非简单请求,浏览器会先发送一个预检请求(OPTIONS方法),询问服务器是否允许实际请求。只有得到肯定答复后,才会发送真正的请求。
2.2 关键HTTP头部解析
CORS涉及的主要HTTP头部包括:
| 头部字段 | 方向 | 说明 |
|---|---|---|
| Origin | 请求头 | 表明请求来源 |
| Access-Control-Allow-Origin | 响应头 | 服务器允许的源(*表示全部) |
| Access-Control-Allow-Methods | 响应头 | 允许的HTTP方法 |
| Access-Control-Allow-Headers | 响应头 | 允许的请求头 |
| Access-Control-Allow-Credentials | 响应头 | 是否允许发送凭据(如cookies) |
| Access-Control-Max-Age | 响应头 | 预检请求缓存时间 |
3. Nginx中的CORS配置实战
3.1 基础配置模板
在Nginx配置文件中(通常在/etc/nginx/nginx.conf或站点配置文件中),我们可以这样设置:
nginx复制server {
listen 80;
server_name api.example.com;
location / {
# 基础CORS设置
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
# 处理OPTIONS预检请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# 其他代理配置...
proxy_pass http://backend;
}
}
3.2 配置项详解
-
Access-Control-Allow-Origin:
*:允许所有域名访问(最宽松但不安全)https://example.com:只允许特定域名(生产环境推荐)
-
Access-Control-Allow-Methods:
- 明确列出允许的HTTP方法,如
GET, POST, PUT, DELETE
- 明确列出允许的HTTP方法,如
-
Access-Control-Allow-Headers:
- 列出客户端可能发送的自定义头部
- 常见的有
Authorization,Content-Type等
-
Access-Control-Max-Age:
- 预检请求的缓存时间(秒)
- 示例中的1728000秒=20天
重要提示:在生产环境中,强烈建议不要使用
*作为Allow-Origin的值,而应该明确指定允许的域名列表。
3.3 多域名动态配置方案
如果需要根据请求来源动态设置允许的域名,可以使用Nginx的map功能:
nginx复制map $http_origin $cors_origin {
default "";
"~^https://example.com" $http_origin;
"~^https://sub.example.com" $http_origin;
"~^https://dev.example.com" $http_origin;
}
server {
location / {
if ($cors_origin) {
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
}
# 其他配置...
}
}
4. 高级配置与性能优化
4.1 带凭证的请求处理
当请求需要携带cookies或HTTP认证信息时,需要特殊配置:
nginx复制add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' 'https://example.com'; # 不能是*
客户端也需要设置:
javascript复制fetch('https://api.example.com/data', {
credentials: 'include'
})
4.2 缓存优化策略
通过合理设置缓存头,减少OPTIONS预检请求:
nginx复制add_header 'Access-Control-Max-Age' 86400; # 缓存1天
4.3 安全加固建议
- 限制允许的方法:
nginx复制add_header 'Access-Control-Allow-Methods' 'GET, POST';
- 限制允许的头部:
nginx复制add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
- 结合Nginx的auth模块进行额外保护
5. 常见问题排查指南
5.1 典型错误与解决方案
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 403 Forbidden | 未正确处理OPTIONS请求 | 确保OPTIONS请求返回204 |
| Missing Allow-Origin | Nginx配置未生效 | 检查配置位置,确认无冲突规则 |
| Credentials not allowed | Allow-Origin为*时使用了凭证 | 指定具体域名而非* |
| Header not allowed | 未包含特定请求头 | 在Allow-Headers中添加 |
5.2 调试技巧
- 使用curl测试:
bash复制curl -H "Origin: https://example.com" -I https://api.example.com/data
- 检查响应头是否包含:
code复制Access-Control-Allow-Origin: https://example.com
- 浏览器开发者工具中查看Network标签:
- 确认OPTIONS请求是否成功
- 检查响应头是否符合预期
6. 实际应用场景示例
6.1 前后端分离项目配置
典型的前后端分离架构中:
- 前端:
https://web.example.com - API:
https://api.example.com
Nginx配置要点:
nginx复制server {
listen 443 ssl;
server_name api.example.com;
# SSL配置...
location / {
add_header 'Access-Control-Allow-Origin' 'https://web.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend;
}
}
6.2 静态资源跨域访问
对于CDN上的字体、图片等静态资源:
nginx复制location ~* \.(eot|ttf|woff|woff2|png|jpg|jpeg|gif|ico)$ {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET';
expires 30d;
}
7. Nginx配置最佳实践
-
按需开放:不要盲目使用
*,根据实际需求开放最小权限 -
配置位置:
- 通用配置放在server块
- 特殊需求放在location块
-
性能考量:
- 合理设置Access-Control-Max-Age
- 避免在每个请求中添加不必要的头部
-
安全建议:
- 结合Nginx的limit_req模块防止滥用
- 定期检查配置有效性
-
测试验证:
- 使用多种浏览器测试
- 验证不同HTTP方法的支持情况
在配置完成后,务必执行nginx -t测试配置,然后systemctl reload nginx重新加载配置。
