同源政策 (Same Origin Policy) 及 CORS 的 3 種解決方案
網頁開發在網頁開發的過程中,我們經常需要從不同來源請求資源。 然而,由於同源政策 (Same Origin Policy) 的限制,這些跨網域的請求常常會被瀏覽器攔截。
同源政策是瀏覽器的一項基本安全功能,用來防止惡意網站讀取或操作其他網站的敏感資料。 這項政策對網絡安全至關重要,但在需要訪問第三方 API 或服務時,這常常造成諸多不便。 本文介紹幾種常見的方法可以解決或繞過 CORS (跨來源資源共享) 問題。
如何判斷是否同源?
Mozilla 網站上有一個列表,可以幫助您理解兩個網址是否同源,假設 A 網址為 http://store.company.com/dir/page.html,下表列出與這個網址是否同源。
URL | 是否同源 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 是 | |
http://store.company.com/dir/inner/another.html | 是 | |
https://store.company.com/secure.html | 否 | scheme 不同 |
http://store.company.com:81/dir/etc.html | 否 | port 不同 |
http://news.company.com/dir/other.html | 否 | domain 不同 |
* scheme、domain、port 都要一樣才會被視為同源,否則為不同源。
方案一: 使用 Access-Control-Allow-Origin
解決 CORS 問題最直接的方法就是在伺服器端的 HTTP header 設置 Access-Control-Allow-Origin,此設置指示瀏覽器允許哪些來源的請求訪問資源。 例如,設置為 * 可以允許所有來源的請求,但出於安全考慮,建議僅允許特定的來源。 以下以 NGINX 為例:
# 開放特定來源
add_header 'Access-Control-Allow-Origin' 'http://www.example.com' always;
# 允許的請求方法
add_header 'Access-Control-Allow-Methods' 'GET, POST, PATCH, DELETE, PUT, OPTIONS' always;
# 允許的請求 Header
add_header 'Access-Control-Allow-Headers' '*' always;
# 是否允許發送 Cookie,如果為允許發送 cookie,Access-Control-Allow-Origin 就不能設定為 *,必須明確指定要開放的網址清單。
add_header 'Access-Control-Allow-Credentials' false;
# Preflight Request 的快取時間,以秒為單位
add_header 'Access-Control-Max-Age' 1728000;
方案二: 使用代理伺服器
由於同源政策只作用在瀏覽器上,因此只要不要透過瀏覽器 (前端 JavaScript) 取得跨域資料就好了。 而要達成這個目的,您可以自己在後端撰寫代理程式 (例如:PHP + CURL),也可以直接使用代理伺服器就可以了。 以下以 NGINX 為例:
# 用於 WebSocket
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 8000;
location / {
# 假設您的網頁程式是 443 Port
# 而代理伺服器是 8000 Port
# 這樣也算跨源,所以也需要設定以下 Header
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PATCH, DELETE, PUT, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' '*' always;
add_header 'Access-Control-Allow-Credentials' false;
add_header 'Access-Control-Max-Age' 1728000;
# 實際的遠端 API 伺服器
proxy_pass https://api.example.com;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade; # 用於 WebSocket
proxy_set_header Connection $connection_upgrade; # 用於 WebSocket
proxy_redirect http://$http_host/ https://$http_host/;
proxy_connect_timeout 1800s;
proxy_send_timeout 1800s;
proxy_read_timeout 1800s;
}
}
以上使用後端 8000 Port 代理遠端 https://api.example.com API 服務,當然您也可以用 URL 子目錄的方式進行代理。 後續前端呼叫 API 時,將直接與代理伺服器溝通,而不是原始的 https://api.example.com 網址。
方案三: 使用 JSONP
JSONP (JSON with Padding) 是一種跨網域請求資料的方法,用於繞過 CORS 限制。 它通過動態建立 script 標籤並指定跨域 URL 作為 src 屬性來工作。 由於 script 標籤不受同源政策限制,因此可以透過此方法從不同來源獲取資料。 不過,這種方法只適用於 GET 請求,且需要伺服器支援 JSONP。 由於安全性問題,不建議在專案中使用此方法,以下範例看看就好。
前端程式
// JSONP 與後端請求完,會執行這個 Function
function jsonpCallback(data) {
console.log('獲取的資料:', data);
}
var script = document.createElement('script');
script.src = 'http://example.com/data?callback=jsonpCallback';
document.body.appendChild(script);
後端程式
<?php
// 從請求 URL 中取得 callback 參數
$callback = filter_input(INPUT_GET, 'callback');
// 將資料轉換成 JSON 格式
$jsonData = json_encode(['name' => 'Test', 'age' => 30]);
// 輸出封裝了 JSON 資料的 CallBack 函數調用
header('Content-Type: application/javascript');
echo $callback . '(' . $jsonData . ');';
0 則留言