欢迎光临马鞍山中国转运服务网
详情描述

Nginx 解决跨域问题全过程

1. 跨域问题原理

跨域问题是由浏览器的同源策略引起的,当协议、域名、端口任意一个不同时,浏览器会阻止跨域请求。

2. Nginx 解决跨域的两种主要方式

方式一:反向代理(最常用)

原理:让同源策略的请求变为同源,通过 Nginx 将不同域的请求代理到同一域下。

配置示例

server {
    listen       80;
    server_name  localhost;  # 前端域名

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    # 代理 API 请求,解决跨域
    location /api/ {
        # 添加跨域请求头
        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;

        # 处理 OPTIONS 预检请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }

        # 反向代理到后端服务
        proxy_pass http://backend-server:8080/api/;

        # 其他代理设置
        proxy_set_header Host $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_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

# 后端服务配置
upstream backend-server {
    server 192.168.1.100:8080;
    server 192.168.1.101:8080;
}

前端调用示例

// 原本跨域调用
// fetch('http://api.example.com:8080/api/data')

// 通过 Nginx 代理后(同源调用)
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data));
方式二:CORS 响应头配置

原理:在后端服务前配置 Nginx,添加 CORS 响应头。

配置示例

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

    # 允许的域名(支持多个)
    set $cors_origin "";
    if ($http_origin ~* "^https?://(localhost|app\.example\.com|www\.example\.com)(:\d+)?$") {
        set $cors_origin $http_origin;
    }

    location / {
        # CORS 配置
        add_header 'Access-Control-Allow-Origin' $cors_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,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Data-Type,X-Requested-With,X-Data-Type,X-Auth-Token' always;
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;

        # 处理 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://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

3. 完整部署步骤

步骤 1:安装 Nginx
# Ubuntu/Debian
sudo apt update
sudo apt install nginx

# CentOS/RHEL
sudo yum install nginx

# macOS
brew install nginx
步骤 2:配置 Nginx

编辑配置文件

sudo vi /etc/nginx/nginx.conf
# 或
sudo vi /usr/local/etc/nginx/nginx.conf

完整示例配置


# /etc/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_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

# 虚拟主机配置
include /etc/nginx/conf.d/*.conf;

# 限制上传大小
client_max_body_size 20M;

}


3. **创建站点配置文件**:
```bash
sudo vi /etc/nginx/conf.d/myapp.conf
# myapp.conf - 完整的前后端分离配置示例

# 前端服务
server {
    listen 80;
    server_name app.example.com;
    root /var/www/html;
    index index.html;

    # 前端路由支持(Vue/React 等 SPA)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 静态资源缓存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # API 代理(解决跨域)
    location /api/ {
        # 移除请求路径中的 /api 前缀
        rewrite ^/api/(.*)$ /$1 break;

        # CORS 配置
        add_header 'Access-Control-Allow-Origin' 'http://app.example.com' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always;

        # 预检请求处理
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Max-Age' 86400;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        # 代理到后端
        proxy_pass http://backend-api;
        proxy_set_header Host $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_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;

        # WebSocket 支持(如果需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # 上传文件代理
    location /upload/ {
        client_max_body_size 50M;
        proxy_pass http://backend-api/upload/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# 后端 API 服务集群
upstream backend-api {
    least_conn;  # 最少连接负载均衡
    server 192.168.1.100:3000 weight=3;
    server 192.168.1.101:3000 weight=2;
    server 192.168.1.102:3000 weight=1;

    # 健康检查
    keepalive 32;
}
步骤 3:部署文件
# 创建前端目录
sudo mkdir -p /var/www/html

# 上传前端文件
sudo cp -r dist/* /var/www/html/

# 设置权限
sudo chown -R nginx:nginx /var/www/html
sudo chmod -R 755 /var/www/html
步骤 4:测试配置并重启
# 测试配置文件语法
sudo nginx -t

# 重新加载配置(不中断服务)
sudo nginx -s reload

# 或重启 Nginx
sudo systemctl restart nginx

# 查看状态
sudo systemctl status nginx
步骤 5:验证跨域是否解决
// 前端测试代码
fetch('/api/test')
  .then(response => {
    console.log('响应头:', response.headers);
    return response.json();
  })
  .then(data => {
    console.log('数据:', data);
  })
  .catch(error => {
    console.error('错误:', error);
  });

4. 高级配置选项

4.1 根据环境变量配置
# 根据环境变量设置不同的 CORS 策略
map $http_origin $cors_header {
    default "";
    "~^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$" "$http_origin";
    "~^https?://(.*\.example\.com)$" "$http_origin";
    "~^https?://(.*\.test\.com)$" "$http_origin";
}

server {
    location /api/ {
        if ($cors_header != "") {
            add_header 'Access-Control-Allow-Origin' $cors_header;
            add_header 'Access-Control-Allow-Credentials' 'true';
        }

        # ... 其他配置
    }
}
4.2 限流和防护
# 限制 API 请求频率
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

location /api/ {
    # 应用限流
    limit_req zone=api burst=20 nodelay;

    # 防止盗链
    valid_referers none blocked app.example.com;
    if ($invalid_referer) {
        return 403;
    }

    # ... 其他配置
}

5. 常见问题和解决方案

问题 1:预检请求失败

症状:浏览器发送 OPTIONS 请求返回 404 或 405

解决

location /api/ {
    # 显式处理 OPTIONS 请求
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Max-Age' 86400;
        add_header 'Content-Length' 0;
        return 204;
    }

    # ... 其他配置
}
问题 2:携带 Cookie 失败

症状:设置了 withCredentials: true 但 Cookie 未发送

解决

add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Origin' 'http://app.example.com' always;
# 注意:当使用 Allow-Credentials 时,Allow-Origin 不能使用通配符 *
问题 3:WebSocket 跨域
location /ws/ {
    proxy_pass http://backend-api;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;

    # WebSocket 超时设置
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

6. Docker 部署示例

# Dockerfile
FROM nginx:alpine

# 复制配置文件
COPY nginx.conf /etc/nginx/nginx.conf
COPY myapp.conf /etc/nginx/conf.d/
COPY dist/ /usr/share/nginx/html/

# 暴露端口
EXPOSE 80

# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]
# docker-compose.yml
version: '3.8'
services:
  nginx:
    build: .
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./logs:/var/log/nginx
    restart: always
    networks:
      - app-network

  backend:
    image: backend-api:latest
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

7. 安全建议

不要使用通配符:生产环境避免使用 Access-Control-Allow-Origin: * 限制允许的域名:明确列出允许的源 使用 HTTPS:生产环境强制使用 HTTPS 设置适当的安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;

通过以上配置,Nginx 可以有效地解决跨域问题,同时提供负载均衡、缓存、安全防护等功能。