本文梳理跨域的背景、反向代理与服务端 CORS 的配置方式,并给出在 Nginx 中更健壮的示例。重点修正了容易踩坑的点( * 的使用、 add_header 的生效条件、预检响应等)。
跨域本质
浏览器基于同源策略限制不同源的资源访问,跨域指“协议/域名/端口”任一不同。
CORS 通过约定的响应头让浏览器在安全前提下放行跨域请求。
区分两类请求:
简单请求: GET/HEAD/POST 且 headers/Content-Type 符合规范,浏览器直接发出请求。
非简单请求:需先发 OPTIONS 预检,服务端通过预检响应头告知是否允许后,再发真实请求。
解决思路
反向代理:让前后端看起来“同源”,降低跨域问题的出现频率。
服务端启用 CORS:在 API 层返回正确的 CORS 响应头(包含预检处理),是根本解决方案。
反向代理示例(客户端视角)
把 localhost:3003 反代到前端 5500 ,把部分路径再转发到后端 3000 :
server {
listen 3003;
server_name localhost;
# 精确匹配根路径,转发到前端
location = / {
proxy_pass http://localhost:5500;
}
# 以 /no 开头的路径转发到后端
location /no {
proxy_pass http://localhost:3000;
}
# /ok/ 表示以 ok 开头的前缀路径(/ok2 不匹配,/ok/son 匹配)
location /ok/ {
proxy_pass http://localhost:3000;
}
}说明:访问 localhost:3003 实际是访问前端 5500 ;访问 localhost:3003/no 则代理到后端 3000 的相应路径。
CORS 标准配置(服务端视角)
常见坑:
Access-Control-Allow-Origin 与 Allow-Credentials=true 同时出现时不可用 * ,需回显具体域。
add_header 默认仅对某些状态码生效,建议加上 always ,确保预检/错误也带头。
预检建议 return 204 (无正文), 200 也可但不必返回实体。
通用模板(支持预检、带 Cookie):
server {
listen 3002;
server_name localhost;
location /ok {
proxy_pass http://localhost:3000;
# 预检请求(非简单请求会在正式请求前触发)
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS always;
add_header Access-Control-Allow-Headers $http_access_control_request_headers always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Max-Age 3600 always; # 预检缓存时间
return 204;
}
# 正式请求的 CORS 头
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always; # 若不需要 cookie,可去掉
add_header Access-Control-Expose-Headers Content-Length,Content-Range always;
}
}补充:
简单请求且不带 Cookie 时,通常只需 Access-Control-Allow-Origin 即可。
若你明确知道允许的方法/头,建议改为静态列出而不是完全回显(更安全)。
多域名白名单
通过 map 将来源域名白名单化,避免无条件回显任意 Origin :
map $http_origin $cors_origin {
default ""; # 不在白名单则不返回该头
~^https?://(www\.itbiancheng\.com|love\.itbiancheng\.com)$ $http_origin;
}
server {
listen 80;
server_name www.itbiancheng.com;
root /nginx;
location / {
# 预检
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS always;
add_header Access-Control-Allow-Headers $http_access_control_request_headers always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Max-Age 3600 always;
return 204;
}
# 正式请求
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials true always;
proxy_pass http://localhost:3000;
}
}说明:
default "" 可避免对非白名单来源注入无效的 Access-Control-Allow-Origin ;值为空时 Nginx 通常不发该头。
若不需要携带 Cookie,可移除 Allow-Credentials 并允许 Origin: (此场景下可以用 )。
常见问题与建议
不要用 Access-Control-Allow-Methods: * ,该头应列出具体方法(如 GET,POST,OPTIONS )。
预检命令应短路,不必上游代理;使用 return 204 即可。
若返回错误(4xx/5xx)也希望携带 CORS 头,必须在 add_header 上加 always 。
对于复杂的自定义请求头,除了回显 $http_access_control_request_headers 也可静态列出白名单以增强安全性。
验证示例(curl)
预检:
curl -i -X OPTIONS \
-H "Origin: https://www.itbiancheng.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Authorization,Content-Type" \
http://localhost:3002/ok正式请求:
curl -i \
-H "Origin: https://www.itbiancheng.com" \
-H "Authorization: Bearer xxx" \
http://localhost:3002/ok