跨域问题是由浏览器的同源策略引起的,当协议、域名、端口任意一个不同时,浏览器会阻止跨域请求。
原理:让同源策略的请求变为同源,通过 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;
}
}
# 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);
});
# 根据环境变量设置不同的 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;
}
# ... 其他配置
}
症状:浏览器发送 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;
}
# 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
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 可以有效地解决跨域问题,同时提供负载均衡、缓存、安全防护等功能。