nginx

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > nginx > Nginx Access-Control-Allow-Origin跨域

Nginx解决Access-Control-Allow-Origin跨域问题完全指南

作者:Getgit

跨域问题是由于浏览器的同源策略(Same-Origin Policy)限制导致的,本文给大家介绍Nginx解决Access-Control-Allow-Origin跨域问题完全指南,感兴趣的朋友跟随小编一起看看吧

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

一、跨域问题概述

1.1 什么是跨域问题?

跨域问题是由于浏览器的同源策略(Same-Origin Policy)限制导致的。当A服务器的前端页面尝试访问B服务器的API时,浏览器会阻止这种跨域请求。

同源策略要求:

只要其中一项不同,即为跨域请求。

二、Nginx解决方案详解

2.1 完整的Nginx配置示例

# nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
    worker_connections 1024;
}
http {
    # 包含MIME类型定义
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    access_log /var/log/nginx/access.log main;
    # 基本优化设置
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    # 主服务器配置
    server {
        listen 9800;
        server_name localhost;
        root /usr/share/nginx/html;
        # 默认首页
        index index.html index.htm;
        # ========== CORS跨域配置 ==========
        # 处理OPTIONS预检请求
        location / {
            # 如果是OPTIONS请求,直接返回204
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Origin' $http_origin;
                add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH';
                add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With, X-Auth-Token, X-CSRF-Token, X-API-Key';
                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;
            }
            # 非OPTIONS请求,添加CORS头部
            add_header 'Access-Control-Allow-Origin' $http_origin;
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH';
            add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With, X-Auth-Token, X-CSRF-Token, X-API-Key';
            add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range';
            # 如果是前端静态文件,直接返回
            try_files $uri $uri/ /index.html;
        }
        # ========== 反向代理配置(代理B服务器API) ==========
        # 方式1:通用API代理(推荐)
        location ~ ^/api/(.*)$ {
            proxy_pass http://192.168.X.XXX:9830/$1$is_args$args;
            proxy_redirect off;
            # 代理设置
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host $server_name;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            # 超时设置
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
            # 缓冲区设置
            proxy_buffer_size 128k;
            proxy_buffers 4 256k;
            proxy_busy_buffers_size 256k;
            # CORS头部(只在响应时添加)
            add_header 'Access-Control-Allow-Origin' $http_origin always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH' always;
            add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With, X-Auth-Token, X-CSRF-Token, X-API-Key' always;
            add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range' always;
            # 处理OPTIONS预检请求
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Origin' $http_origin;
                add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH';
                add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With, X-Auth-Token, X-CSRF-Token, X-API-Key';
                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;
            }
        }
        # 方式2:特定路径代理(原示例中的配置)
        location ~ /quartz/ {
            proxy_pass http://192.168.X.XXX:9830;
            proxy_read_timeout 360s;
            proxy_send_timeout 360s;
            # 代理头部
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Host $server_name;
            # CORS头部配置
            add_header Front-End-Https on;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH' always;
            add_header 'Access-Control-Allow-Origin' $http_origin always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Allow-Headers' 'Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Mx-ReqToken, X-Requested-With, X-Auth-Token, X-CSRF-Token, X-API-Key' always;
            add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range, X-Total-Count' always;
            # 缓存预检请求
            add_header 'Access-Control-Max-Age' 1728000;
        }
        # 错误页面配置
        error_page 404 /404.html;
        location = /404.html {
            root /usr/share/nginx/html;
        }
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root /usr/share/nginx/html;
        }
    }
    # ========== 多个后端服务配置示例 ==========
    server {
        listen 9801;
        server_name api-proxy.example.com;
        # 代理多个后端服务
        location /service1/ {
            proxy_pass http://backend1:8080/;
            # ... CORS配置同上
        }
        location /service2/ {
            proxy_pass http://backend2:8081/;
            # ... CORS配置同上
        }
    }
}

2.2 配置详解

2.2.1 核心CORS头部说明

# 允许的源(域名)
add_header 'Access-Control-Allow-Origin' $http_origin;
# 允许的HTTP方法
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH';
# 允许的请求头
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With';
# 是否允许发送Cookie
add_header 'Access-Control-Allow-Credentials' 'true';
# 预检请求缓存时间(秒)
add_header 'Access-Control-Max-Age' 1728000;
# 暴露给客户端的响应头
add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range';

2.2.2 变量说明

变量说明示例值
$http_origin请求头中的Origin字段http://localhost:8080
$request_method请求方法GET, POST, OPTIONS
$http_host请求的Host头example.com:9800
$remote_addr客户端IP地址192.168.1.100
$scheme请求协议http, https

三、不同场景的配置方案

3.1 场景1:开发环境(允许所有源)

# 开发环境配置 - 允许所有来源
map $http_origin $cors_origin {
    default "*";
}
server {
    listen 9800;
    location /api/ {
        # 开发环境:允许所有来源
        add_header 'Access-Control-Allow-Origin' $cors_origin;
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' '*';
        add_header 'Access-Control-Allow-Headers' '*';
        # 处理OPTIONS请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        proxy_pass http://backend:8080;
    }
}

3.2 场景2:生产环境(白名单限制)

# 生产环境配置 - 白名单控制
map $http_origin $cors_origin {
    # 默认不允许
    default "";
    # 允许的域名
    ~^https?://(www\.)?example\.com(:[0-9]+)?$ $http_origin;
    ~^https?://api\.example\.com(:[0-9]+)?$ $http_origin;
    ~^https?://localhost(:[0-9]+)?$ $http_origin;
    ~^https?://127\.0\.0\.1(:[0-9]+)?$ $http_origin;
}
server {
    listen 9800;
    location /api/ {
        # 只有在白名单内才添加CORS头部
        if ($cors_origin != "") {
            add_header 'Access-Control-Allow-Origin' $cors_origin;
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With';
        }
        # 处理OPTIONS请求
        if ($request_method = 'OPTIONS') {
            if ($cors_origin != "") {
                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' 'Authorization, Content-Type, Accept, X-Requested-With';
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Content-Length' 0;
                return 204;
            }
        }
        proxy_pass http://backend:8080;
    }
}

3.3 场景3:微服务架构

# 微服务网关配置
upstream auth_service {
    server auth-service:8081;
}
upstream order_service {
    server order-service:8082;
}
upstream product_service {
    server product-service:8083;
}
server {
    listen 9800;
    # 认证服务
    location /auth/ {
        proxy_pass http://auth_service/;
        include /etc/nginx/conf.d/cors.conf;
    }
    # 订单服务
    location /orders/ {
        proxy_pass http://order_service/;
        include /etc/nginx/conf.d/cors.conf;
    }
    # 商品服务
    location /products/ {
        proxy_pass http://product_service/;
        include /etc/nginx/conf.d/cors.conf;
    }
}
# cors.conf - 统一的CORS配置
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With, X-Auth-Token';
add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range, X-Total-Count';
if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' $http_origin;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH';
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With, X-Auth-Token';
    add_header 'Access-Control-Max-Age' 1728000;
    add_header 'Content-Length' 0;
    return 204;
}

四、常见问题与解决方案

4.1 问题1:add_header不生效

原因: Nginx的add_header指令在错误页面或某些条件下可能不生效。

解决方案: 使用always参数

# 使用always确保头部始终添加
add_header 'Access-Control-Allow-Origin' $http_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;

4.2 问题2:携带Cookie时跨域失败

原因: 当使用Access-Control-Allow-Credentials: true时,不能使用通配符*作为源。

解决方案: 动态设置允许的源

# 动态获取请求的Origin
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
# 或者使用条件判断
if ($http_origin ~* (example\.com|localhost)) {
    add_header 'Access-Control-Allow-Origin' $http_origin;
    add_header 'Access-Control-Allow-Credentials' 'true';
}

4.3 问题3:OPTIONS预检请求返回404

原因: 后端服务没有处理OPTIONS请求。

解决方案: 在Nginx层面处理OPTIONS请求

location /api/ {
    # 单独处理OPTIONS请求
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' $http_origin;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Length' 0;
        return 204;
    }
    # 正常请求转发
    proxy_pass http://backend:8080;
}

4.4 问题4:WebSocket跨域问题

解决方案: 特殊处理WebSocket连接

location /ws/ {
    proxy_pass http://backend:8080;
    proxy_http_version 1.1;
    # WebSocket升级
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    # CORS配置
    add_header 'Access-Control-Allow-Origin' $http_origin;
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Authorization, Upgrade, Connection';
}

五、测试与验证

5.1 测试脚本

// test-cors.html
<!DOCTYPE html>
<html>
<head>
    <title>CORS测试</title>
</head>
<body>
    <h1>CORS测试页面</h1>
    <button onclick="testCORS()">测试跨域请求</button>
    <div id="result"></div>
    <script>
        async function testCORS() {
            try {
                const response = await fetch('http://A服务器IP:9800/api/test', {
                    method: 'GET',
                    credentials: 'include', // 携带Cookie
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': 'Bearer test-token'
                    }
                });
                const data = await response.json();
                document.getElementById('result').innerHTML = 
                    `<pre>成功: ${JSON.stringify(data, null, 2)}</pre>`;
            } catch (error) {
                document.getElementById('result').innerHTML = 
                    `<pre style="color:red">错误: ${error.message}</pre>`;
            }
        }
        // 预检请求测试
        async function testPreflight() {
            try {
                const response = await fetch('http://A服务器IP:9800/api/test', {
                    method: 'PUT',
                    credentials: 'include',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-Custom-Header': 'custom-value'
                    },
                    body: JSON.stringify({ test: 'data' })
                });
                const data = await response.json();
                console.log('预检请求成功:', data);
            } catch (error) {
                console.error('预检请求失败:', error);
            }
        }
    </script>
</body>
</html>

5.2 使用curl测试

# 测试OPTIONS预检请求
curl -X OPTIONS http://A服务器IP:9800/api/test \
  -H "Origin: http://localhost:8080" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: authorization,content-type" \
  -v
# 测试正常请求
curl -X GET http://A服务器IP:9800/api/test \
  -H "Origin: http://localhost:8080" \
  -H "Authorization: Bearer test-token" \
  -v

5.3 Nginx配置检查

# 检查nginx配置语法
nginx -t
# 重新加载配置(不重启)
nginx -s reload
# 查看nginx错误日志
tail -f /var/log/nginx/error.log
# 查看访问日志
tail -f /var/log/nginx/access.log

六、安全注意事项

6.1 安全建议

  1. 不要使用通配符*:在生产环境中,应明确指定允许的域名
  2. 限制允许的方法:只开放必要的HTTP方法
  3. 限制允许的头部:只允许必要的请求头
  4. 设置合理的缓存时间Access-Control-Max-Age不宜设置过长
  5. 使用HTTPS:跨域请求应使用HTTPS加密传输

6.2 安全配置示例

# 安全增强的CORS配置
map $http_origin $allowed_origin {
    # 明确允许的域名列表
    "https://www.example.com" "https://www.example.com";
    "https://api.example.com" "https://api.example.com";
    "https://staging.example.com" "https://staging.example.com";
    default "";
}
server {
    # ... 其他配置 ...
    location /api/ {
        # 仅对允许的源添加CORS头部
        if ($allowed_origin != "") {
            add_header 'Access-Control-Allow-Origin' $allowed_origin;
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST';
            add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
            add_header 'Access-Control-Expose-Headers' 'X-RateLimit-Limit, X-RateLimit-Remaining';
            add_header 'Access-Control-Max-Age' 86400; # 24小时
        }
        # 安全相关的其他头部
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-XSS-Protection "1; mode=block" always;
        proxy_pass http://backend:8080;
    }
}

七、总结

通过Nginx配置解决跨域问题是最常用且有效的方法之一。关键点总结:

通过合理配置Nginx,不仅可以解决跨域问题,还能提升系统的安全性、性能和可维护性。

到此这篇关于Nginx解决Access-Control-Allow-Origin跨域问题完全指南的文章就介绍到这了,更多相关Nginx Access-Control-Allow-Origin跨域内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文