我們以一個簡單的例子模擬不同情況下產生的跨域問題以及解決方案。假設在http://127.0.0.1:8000的頁面調用接口
fetch('http://127.0.0.1:8003/api/data')
常看到的錯誤“Access to fetch at ‘http://127.0.0.1:8003/api/data’ from origin ‘http://localhost:8000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.”。這說明前端嘗試訪問的 API 服務,沒有正確配置跨域資源共享(CORS)頭。按如下配置:
server {listen 8003;server_name yourdomain.com;location / {# 允許所有來源訪問(生產環境應限制為特定域名)add_header 'Access-Control-Allow-Origin' '*';# 允許的請求方法add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; # 允許的請求頭add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';# 預檢請求(OPTIONS)緩存時間add_header 'Access-Control-Max-Age' 1728000;}
}
并重新加載配置nginx -s reload,問題解決。接下來,我們對請求參數做下修改,并重新發起請求:
var options = {method: 'GET', // 請求類型headers: {'Custom-Header': '123', // 自定義header參數}
}
fetch('http://127.0.0.1:8003/api/data',options)
Access to fetch at ‘http://127.0.0.1:8003/api/data’ from origin ‘http://localhost:8000’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
HTTP 請求根據是否觸發 CORS 預檢(Preflight)分為 簡單請求(Simple Request) 和 復雜請求(Complex Request)。它們的核心區別在于瀏覽器是否會先發送 OPTIONS 預檢請求。 因為我們在請求頭里增加了的參數Custom-Header,這樣請求就從一個簡單請求變成了復雜請求。上述錯誤顯示預請求失敗,我們重新修改nginx配置:
http {include mime.types;default_type application/octet-stream;map $http_origin $cors_origin {default "*"; # 默認值(如果 $http_origin 不存在)"~^https?://(localhost|example\.com|sub\.example\.com)(:[0-9]+)?$" $http_origin;"~^http?://(localhost|example\.com|sub\.example\.com)(:[0-9]+)?$" $http_origin;}server {listen 8003;server_name localhost;location /api/data {add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; add_header 'Access-Control-Allow-Credentials' 'true';add_header 'Access-Control-Max-Age' 1728000; # 預檢緩存20天if ($request_method = 'OPTIONS') {add_header 'Access-Control-Allow-Origin' $cors_origin;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;}# 允許所有來源訪問(生產環境應限制為特定域名)add_header 'Access-Control-Allow-Origin' '*';alias /Users/wk/data.json;default_type application/json;add_header Cache-Control "no-cache";}}
再發請求剛才的問題解決了,但又出現了新問題:
Access to fetch at ‘http://127.0.0.1:8003/api/data’ from origin ‘http://localhost:8000’ has been blocked by CORS policy: Request header field custom-header is not allowed by Access-Control-Allow-Headers in preflight response.”。
說明Access-Control-Allow-Headers不允許header里包含custom-header參數,需要在Access-Control-Allow-Headers里增加custom-header參數。注意Access-Control-Allow-Headers一定要放到if ($request_method = ‘OPTIONS’)語句內,否則不生效。因為CORS 配置的特殊要求,順序尤為重要,預檢請求(OPTIONS) 的響應頭必須完整。
server {listen 8003;server_name localhost;location /api/data {add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; add_header 'Access-Control-Allow-Credentials' 'true';add_header 'Access-Control-Max-Age' 1728000; # 預檢緩存20天if ($request_method = 'OPTIONS') {add_header 'Access-Control-Allow-Origin' $cors_origin;add_header 'Access-Control-Allow-Headers' 'Content-Type, Custom-Header,Authorization';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;}# 允許所有來源訪問(生產環境應限制為特定域名)add_header 'Access-Control-Allow-Origin' '*';alias /Users/wk/data.json;default_type application/json;add_header Cache-Control "no-cache";}}
上述問題得以解決。我們再改下請求參數,增加 credentials: “include”。:
var options = {method: 'GET', // 請求類型credentials: "include",headers: {'Custom-Header': '123', // 自定義header參數'Content-Type': 'application/json' // 通常需要設置的內容類型}
}
fetch('http://127.0.0.1:8003/api/data',options)
credentials: “include” 的作用是:控制發送憑據,在跨域請求中攜帶當前域的 cookies、HTTP 認證頭(如 Authorization)等敏感信息。但它有特殊要求,服務器需顯式允許響應頭包含 Access-Control-Allow-Credentials: true,Access-Control-Allow-Origin 不能為通配符 ,必須明確指定請求來源域名。否則就會錯誤類似的錯誤:
Access to fetch at ‘http://127.0.0.1:8003/api/data’ from origin ‘http://127.0.0.1:8000’ has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard '’ when the request’s credentials mode is ‘include’。
解決方法:
http {include mime.types;default_type application/octet-stream;map $http_origin $cors_origin {default "*"; # 默認值(如果 $http_origin 不存在)"~^https?://(localhost|example\.com|sub\.example\.com)(:[0-9]+)?$" $http_origin;"~^http?://(localhost|example\.com|sub\.example\.com)(:[0-9]+)?$" $http_origin;}server {listen 8003;server_name localhost;location /api/data {add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; add_header 'Access-Control-Allow-Origin' $cors_origin;add_header 'Access-Control-Max-Age' 1728000; # 預檢緩存20天if ($request_method = 'OPTIONS') {add_header 'Access-Control-Allow-Origin' $cors_origin;add_header 'Access-Control-Allow-Headers' 'custom-header, content-type, authorization';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;}add_header 'Access-Control-Allow-Credentials' 'true';alias /Users/wk/data.json;default_type application/json;add_header Cache-Control "no-cache";}}
總結配置nginx支持跨域請求需要注意的幾個要點:
- 基礎 CORS 頭配置,必須返回的頭部:
add_header 'Access-Control-Allow-Origin' '$http_origin'; # 或指定具體域名
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; # 按需擴展
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization'; # 包含客戶端實際使用的頭
Allow-Origin 不能同時使用 * 和 Allow-Credentials: true。
Allow-Methods 和 Allow-Headers 需明確列出(不能為 *)。
- 預檢請求(OPTIONS)處理
if ($request_method = 'OPTIONS') {add_header 'Access-Control-Allow-Origin' '$http_origin';add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, OPTIONS'; # 與方法列表一致add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Custom-Header';add_header 'Access-Control-Max-Age' 86400; # 預檢緩存時間(秒)return 204; # 空響應
}
- 帶憑據的請求(Credentials)
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' $http_origin; # 動態獲取域名
Access-Control-Allow-Origin明確域名,不能用 *。Cookie 需設置 SameSite=None; Secur(HTTPS 環境)。
- 動態來源管理,使用 map 匹配合法來源:
map $http_origin $cors_origin {default "";"~^https://(www\.)?example\.com$" $http_origin;"~^http://localhost(:\d+)?$" $http_origin;
}
server {add_header 'Access-Control-Allow-Origin' $cors_origin;
}