Administrator
发布于 2025-11-03 / 3 阅读
0
0

Nginx 解决跨域(CORS)最佳实践

本文梳理跨域的背景、反向代理与服务端 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

参考


评论