Nginx解決Access-Control-Allow-Origin跨域問題完全指南
提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔
一、跨域問題概述
1.1 什么是跨域問題?
跨域問題是由于瀏覽器的同源策略(Same-Origin Policy)限制導致的。當A服務器的前端頁面嘗試訪問B服務器的API時,瀏覽器會阻止這種跨域請求。
同源策略要求:
- 協議相同(http/https)
- 域名相同
- 端口相同
只要其中一項不同,即為跨域請求。
二、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;
# 基本優(yōu)化設置
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';
# 如果是前端靜態(tài)文件,直接返回
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;
# 緩沖區(qū)設置
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'; # 是否允許發(fā)送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:開發(fā)環(huán)境(允許所有源)
# 開發(fā)環(huán)境配置 - 允許所有來源
map $http_origin $cors_origin {
default "*";
}
server {
listen 9800;
location /api/ {
# 開發(fā)環(huán)境:允許所有來源
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:生產環(huán)境(白名單限制)
# 生產環(huán)境配置 - 白名單控制
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 - 統(tǒng)一的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時,不能使用通配符*作為源。
解決方案: 動態(tài)設置允許的源
# 動態(tài)獲取請求的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;
}
# 正常請求轉發(fā)
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 安全建議
- 不要使用通配符
*:在生產環(huán)境中,應明確指定允許的域名 - 限制允許的方法:只開放必要的HTTP方法
- 限制允許的頭部:只允許必要的請求頭
- 設置合理的緩存時間:
Access-Control-Max-Age不宜設置過長 - 使用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配置解決跨域問題是最常用且有效的方法之一。關鍵點總結:
- 核心原理:通過反向代理和添加CORS響應頭解決跨域
- 必須頭部:
Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Credentials(需要時)
- 處理流程:
- 瀏覽器發(fā)送OPTIONS預檢請求
- Nginx直接響應或轉發(fā)到后端
- 添加必要的CORS頭部
- 允許實際請求通過
- 最佳實踐:
- 開發(fā)環(huán)境可以寬松配置
- 生產環(huán)境需要嚴格的白名單控制
- 結合HTTPS提升安全性
- 定期審查和更新配置
通過合理配置Nginx,不僅可以解決跨域問題,還能提升系統(tǒng)的安全性、性能和可維護性。
到此這篇關于Nginx解決Access-Control-Allow-Origin跨域問題完全指南的文章就介紹到這了,更多相關Nginx Access-Control-Allow-Origin跨域內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Windows安裝nginx1.10.1反向代理訪問IIS網站
這篇文章主要為大家詳細介紹了Windows安裝nginx1.10.1反向代理訪問IIS網站的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11

