Nginx解決Access-Control-Allow-Origin跨域問(wèn)題完全指南
提示:文章寫(xiě)完后,目錄可以自動(dòng)生成,如何生成可參考右邊的幫助文檔
一、跨域問(wèn)題概述
1.1 什么是跨域問(wèn)題?
跨域問(wèn)題是由于瀏覽器的同源策略(Same-Origin Policy)限制導(dǎo)致的。當(dāng)A服務(wù)器的前端頁(yè)面嘗試訪(fǎng)問(wèn)B服務(wù)器的API時(shí),瀏覽器會(huì)阻止這種跨域請(qǐng)求。
同源策略要求:
- 協(xié)議相同(http/https)
- 域名相同
- 端口相同
只要其中一項(xiàng)不同,即為跨域請(qǐng)求。
二、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類(lèi)型定義
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)化設(shè)置
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;
# 主服務(wù)器配置
server {
listen 9800;
server_name localhost;
root /usr/share/nginx/html;
# 默認(rèn)首頁(yè)
index index.html index.htm;
# ========== CORS跨域配置 ==========
# 處理OPTIONS預(yù)檢請(qǐng)求
location / {
# 如果是OPTIONS請(qǐng)求,直接返回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請(qǐ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, 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服務(wù)器API) ==========
# 方式1:通用API代理(推薦)
location ~ ^/api/(.*)$ {
proxy_pass http://192.168.X.XXX:9830/$1$is_args$args;
proxy_redirect off;
# 代理設(shè)置
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";
# 超時(shí)設(shè)置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 緩沖區(qū)設(shè)置
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# CORS頭部(只在響應(yīng)時(shí)添加)
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預(yù)檢請(qǐng)求
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;
# 緩存預(yù)檢請(qǐng)求
add_header 'Access-Control-Max-Age' 1728000;
}
# 錯(cuò)誤頁(yè)面配置
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;
}
}
# ========== 多個(gè)后端服務(wù)配置示例 ==========
server {
listen 9801;
server_name api-proxy.example.com;
# 代理多個(gè)后端服務(wù)
location /service1/ {
proxy_pass http://backend1:8080/;
# ... CORS配置同上
}
location /service2/ {
proxy_pass http://backend2:8081/;
# ... CORS配置同上
}
}
}2.2 配置詳解
2.2.1 核心CORS頭部說(shuō)明
# 允許的源(域名) add_header 'Access-Control-Allow-Origin' $http_origin; # 允許的HTTP方法 add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH'; # 允許的請(qǐng)求頭 add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With'; # 是否允許發(fā)送Cookie add_header 'Access-Control-Allow-Credentials' 'true'; # 預(yù)檢請(qǐng)求緩存時(shí)間(秒) add_header 'Access-Control-Max-Age' 1728000; # 暴露給客戶(hù)端的響應(yīng)頭 add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range';
2.2.2 變量說(shuō)明
| 變量 | 說(shuō)明 | 示例值 |
|---|---|---|
$http_origin | 請(qǐng)求頭中的Origin字段 | http://localhost:8080 |
$request_method | 請(qǐng)求方法 | GET, POST, OPTIONS |
$http_host | 請(qǐng)求的Host頭 | example.com:9800 |
$remote_addr | 客戶(hù)端IP地址 | 192.168.1.100 |
$scheme | 請(qǐng)求協(xié)議 | http, https |
三、不同場(chǎng)景的配置方案
3.1 場(chǎng)景1:開(kāi)發(fā)環(huán)境(允許所有源)
# 開(kāi)發(fā)環(huán)境配置 - 允許所有來(lái)源
map $http_origin $cors_origin {
default "*";
}
server {
listen 9800;
location /api/ {
# 開(kāi)發(fā)環(huán)境:允許所有來(lái)源
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請(qǐng)求
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend:8080;
}
}3.2 場(chǎng)景2:生產(chǎn)環(huán)境(白名單限制)
# 生產(chǎn)環(huán)境配置 - 白名單控制
map $http_origin $cors_origin {
# 默認(rèn)不允許
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/ {
# 只有在白名單內(nèi)才添加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請(qǐng)求
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 場(chǎng)景3:微服務(wù)架構(gòu)
# 微服務(wù)網(wǎng)關(guān)配置
upstream auth_service {
server auth-service:8081;
}
upstream order_service {
server order-service:8082;
}
upstream product_service {
server product-service:8083;
}
server {
listen 9800;
# 認(rèn)證服務(wù)
location /auth/ {
proxy_pass http://auth_service/;
include /etc/nginx/conf.d/cors.conf;
}
# 訂單服務(wù)
location /orders/ {
proxy_pass http://order_service/;
include /etc/nginx/conf.d/cors.conf;
}
# 商品服務(wù)
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;
}四、常見(jiàn)問(wèn)題與解決方案
4.1 問(wèn)題1:add_header不生效
原因: Nginx的add_header指令在錯(cuò)誤頁(yè)面或某些條件下可能不生效。
解決方案: 使用always參數(shù)
# 使用always確保頭部始終添加 add_header 'Access-Control-Allow-Origin' $http_origin always; add_header 'Access-Control-Allow-Credentials' 'true' always;
4.2 問(wèn)題2:攜帶Cookie時(shí)跨域失敗
原因: 當(dāng)使用Access-Control-Allow-Credentials: true時(shí),不能使用通配符*作為源。
解決方案: 動(dòng)態(tài)設(shè)置允許的源
# 動(dòng)態(tài)獲取請(qǐng)求的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 問(wèn)題3:OPTIONS預(yù)檢請(qǐng)求返回404
原因: 后端服務(wù)沒(méi)有處理OPTIONS請(qǐng)求。
解決方案: 在Nginx層面處理OPTIONS請(qǐng)求
location /api/ {
# 單獨(dú)處理OPTIONS請(qǐng)求
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;
}
# 正常請(qǐng)求轉(zhuǎn)發(fā)
proxy_pass http://backend:8080;
}4.4 問(wèn)題4:WebSocket跨域問(wèn)題
解決方案: 特殊處理WebSocket連接
location /ws/ {
proxy_pass http://backend:8080;
proxy_http_version 1.1;
# WebSocket升級(jí)
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';
}五、測(cè)試與驗(yàn)證
5.1 測(cè)試腳本
// test-cors.html
<!DOCTYPE html>
<html>
<head>
<title>CORS測(cè)試</title>
</head>
<body>
<h1>CORS測(cè)試頁(yè)面</h1>
<button onclick="testCORS()">測(cè)試跨域請(qǐng)求</button>
<div id="result"></div>
<script>
async function testCORS() {
try {
const response = await fetch('http://A服務(wù)器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">錯(cuò)誤: ${error.message}</pre>`;
}
}
// 預(yù)檢請(qǐng)求測(cè)試
async function testPreflight() {
try {
const response = await fetch('http://A服務(wù)器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('預(yù)檢請(qǐng)求成功:', data);
} catch (error) {
console.error('預(yù)檢請(qǐng)求失敗:', error);
}
}
</script>
</body>
</html>5.2 使用curl測(cè)試
# 測(cè)試OPTIONS預(yù)檢請(qǐng)求 curl -X OPTIONS http://A服務(wù)器IP:9800/api/test \ -H "Origin: http://localhost:8080" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: authorization,content-type" \ -v # 測(cè)試正常請(qǐng)求 curl -X GET http://A服務(wù)器IP:9800/api/test \ -H "Origin: http://localhost:8080" \ -H "Authorization: Bearer test-token" \ -v
5.3 Nginx配置檢查
# 檢查nginx配置語(yǔ)法 nginx -t # 重新加載配置(不重啟) nginx -s reload # 查看nginx錯(cuò)誤日志 tail -f /var/log/nginx/error.log # 查看訪(fǎng)問(wèn)日志 tail -f /var/log/nginx/access.log
六、安全注意事項(xiàng)
6.1 安全建議
- 不要使用通配符
*:在生產(chǎn)環(huán)境中,應(yīng)明確指定允許的域名 - 限制允許的方法:只開(kāi)放必要的HTTP方法
- 限制允許的頭部:只允許必要的請(qǐng)求頭
- 設(shè)置合理的緩存時(shí)間:
Access-Control-Max-Age不宜設(shè)置過(guò)長(zhǎng) - 使用HTTPS:跨域請(qǐng)求應(yīng)使用HTTPS加密傳輸
6.2 安全配置示例
# 安全增強(qiáng)的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/ {
# 僅對(duì)允許的源添加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小時(shí)
}
# 安全相關(guān)的其他頭部
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;
}
}七、總結(jié)
通過(guò)Nginx配置解決跨域問(wèn)題是最常用且有效的方法之一。關(guān)鍵點(diǎn)總結(jié):
- 核心原理:通過(guò)反向代理和添加CORS響應(yīng)頭解決跨域
- 必須頭部:
Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Credentials(需要時(shí))
- 處理流程:
- 瀏覽器發(fā)送OPTIONS預(yù)檢請(qǐng)求
- Nginx直接響應(yīng)或轉(zhuǎn)發(fā)到后端
- 添加必要的CORS頭部
- 允許實(shí)際請(qǐng)求通過(guò)
- 最佳實(shí)踐:
- 開(kāi)發(fā)環(huán)境可以寬松配置
- 生產(chǎn)環(huán)境需要嚴(yán)格的白名單控制
- 結(jié)合HTTPS提升安全性
- 定期審查和更新配置
通過(guò)合理配置Nginx,不僅可以解決跨域問(wèn)題,還能提升系統(tǒng)的安全性、性能和可維護(hù)性。
到此這篇關(guān)于Nginx解決Access-Control-Allow-Origin跨域問(wèn)題完全指南的文章就介紹到這了,更多相關(guān)Nginx Access-Control-Allow-Origin跨域內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Crontab+Shell做Nginx日志切割腳本實(shí)例代碼
本篇文章主要介紹了Crontab+Shell做Nginx日志切割腳本實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
Nginx配置WebSocket反向代理的實(shí)現(xiàn)示例
本文主要介紹了Nginx配置WebSocket反向代理的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08
Nginx配置多個(gè)端口進(jìn)行監(jiān)聽(tīng)的實(shí)現(xiàn)
隨著容器的應(yīng)用越來(lái)越多,將nginx部署在容器中也是常有之事,本文主要介紹了Nginx配置多個(gè)端口進(jìn)行監(jiān)聽(tīng)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
Windows安裝nginx1.10.1反向代理訪(fǎng)問(wèn)IIS網(wǎng)站
這篇文章主要為大家詳細(xì)介紹了Windows安裝nginx1.10.1反向代理訪(fǎng)問(wèn)IIS網(wǎng)站的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
nginx?某些url只能由特定ip訪(fǎng)問(wèn)的實(shí)現(xiàn)
在Nginx中針對(duì)某些URL只允許特定IP地址訪(fǎng)問(wèn),本文就來(lái)介紹一下如何實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
nginx安裝圖解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了nginx安裝的圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08

