基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

如何在 Nginx 中配置跨域资源共享(CORS)?

在 Nginx 中配置跨域资源共享(CORS),本质上是通过添加特定的 HTTP 响应头(Headers)来告诉浏览器允许跨域请求。

跨域请求通常分为两种:简单请求(如普通的 GET/POST)和预检请求(浏览器在发送复杂请求前,会先发送一个 OPTIONS 方法的请求进行探测)。因此,Nginx 的配置需要同时处理这两种情况。

以下是几种常见的 CORS 配置方案,从简单到复杂:

方案一:允许所有域名跨域(适用于开发环境或公开 API)

这是最基础的配置,允许任何域名访问,并且处理了 OPTIONS 预检请求。

在你需要允许跨域的 location 块中添加以下代码:

plaintext
server {
    listen 80;
    server_name api.example.com;

    location / {
        # 1. 处理 OPTIONS 预检请求
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
            # 允许客户端携带的自定义请求头
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
            # 预检请求的缓存时间(秒),在此期间浏览器无需再次发送预检请求
            add_header 'Access-Control-Max-Age' 86400;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204; # 告诉浏览器预检成功,无需返回实际内容
        }

        # 2. 处理实际的请求(GET/POST等)
        # 注意:加上 always 参数,确保即使后端返回 4xx/5xx 错误,也会带上跨域头
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
        # 允许浏览器读取的响应头
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;

        # 这里是你原本的代理配置
        proxy_pass http://localhost:8080;
    }
}

方案二:指定单一域名且允许携带 Cookie(适用于生产环境)

如果你的跨域请求需要携带 Cookie 或认证信息(withCredentials: true),Access-Control-Allow-Origin 绝对不能设置为 *,必须指定确切的域名,并且需要加上 Access-Control-Allow-Credentials 'true'

plaintext
location / {
    if ($request_method = 'OPTIONS') {
        # 替换为你前端所在的具体域名
        add_header 'Access-Control-Allow-Origin' 'https://www.yourfrontend.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        # 允许携带 Cookie
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Max-Age' 86400;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
    }

    add_header 'Access-Control-Allow-Origin' 'https://www.yourfrontend.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    proxy_pass http://localhost:8080;
}

方案三:动态允许多个指定域名跨域(高级)

如果你有多个前端域名(例如 a.comb.com)需要访问 API,且需要携带 Cookie,你可以结合 Nginx 的 map 指令来动态匹配 Origin

第一步:在 http 块中(server 块之外)定义 map

plaintext
http {
    # ... 其他配置 ...

    # 检查请求的 Origin 是否在允许的列表中
    map $http_origin $cors_origin {
        default ""; # 默认不允许
        "~^https://www\.domainA\.com$" $http_origin; # 精确匹配
        "~^https://www\.domainB\.com$" $http_origin;
        "~^http://localhost:[0-9]+$" $http_origin;   # 允许本地开发的所有端口
    }
    
    server {
        # ...
    }
}

第二步:在 location 块中使用定义好的 $cors_origin 变量:

plaintext
location / {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' $cors_origin;
        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';
        add_header 'Access-Control-Max-Age' 86400;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
    }

    add_header 'Access-Control-Allow-Origin' $cors_origin always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    proxy_pass http://localhost:8080;
}

⚠️ 注意事项与避坑指南

  1. 配置后记得重载 Nginx:
    修改完成后,使用 nginx -t 测试配置是否正确,然后运行 nginx -s reload 使配置生效。
  2. add_header 的覆盖问题(重要):
    Nginx 中如果当前块(如 iflocation)包含 add_header 指令,它会清除掉所有从父级继承来的 add_header。这就是为什么在 if ($request_method = 'OPTIONS') 中我们要把所有头再写一遍的原因。
  3. 不要在应用层(如 Spring Boot, Node.js)和 Nginx 中同时配置 CORS:
    如果你在后端代码里已经写了 CORS 逻辑,又在 Nginx 里配了一遍,浏览器会收到两个 Access-Control-Allow-Origin 头,从而报错(The 'Access-Control-Allow-Origin' header contains multiple values)。推荐只在 Nginx 层或者只在后端应用层做一次处理。
  4. always 参数:
    在处理实际请求的 add_header 后面加上 always。这样即使你的后端服务返回 400、401 或 500 错误,Nginx 也会把跨域头加上。否则,前端只能看到 "CORS error",无法获取到真正的 4xx/5xx 错误状态码。
00:00
00:00