跨網域存取介紹
目前的 Web 應用的發展伴隨著 Mashup 技術而演進,許多知名的網站都提供 Web Service API 進行溝通與資料交換。最常碰到的問題就是「Cross Domain Access (跨網域存取)」議題,由於瀏覽器基於資料安全的原則,為了避免惡意程式碼傳送敏感資料到其他站台,預設策略都會禁止跨網域 Request 連線。如下圖所示:
我們用 Google Chrome 來實驗一下,先寫一個 HTML 放在 http://base.com/cros.php 站台,並且用 Script 實作 Cross Domain 發送 Request,程式如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta content="text/html; charset=utf-8" http-equiv="content-type" /> <title>HTTP Cross-Origin Resource Sharing</title> <script src="jquery-1.8.0.min.js"></script> <script type="text/javascript"> $(document).ready(function() { $('#send-btn').click(function(){ // 按鈕按下時發送一個 POST Request 到 http://hack.com/hack.php $.ajax({ url: 'http://hack.com/hack.php', type: 'POST', contentType: 'application/json', data: '{"data":"' + $('#text-input').val() + '"}' }).done(function() { console.log('Done!'); }); }); }); </script> </head> <body> <input type="text" name="data" value="Secret!" id="text-input" /> <button id="send-btn">Send to Hack Site</button> </body> </html>
按下「Send to Hack Site」之後發生以下錯誤:
XMLHttpRequest cannot load http://hack.com/hack.php. Origin http://base.com is not allowed by Access-Control-Allow-Origin.
Chrome Debug Tools 畫面如下:
果然,被擋了,看來 Hack 失敗!
典型跨網域的解決方法
接下來我們先介紹幾種搞定跨網路資料傳送的方法,要實作跨網域存取有以下幾招:
- Form Submit
- JSONP
- W3C - CORS (Cross-Origin Resource Sharing)
Form Submit 這個方法算是最簡單的,但是寫起來最複雜(程式碼很醜),常常要藏一個 iFrame 來處理回傳資料,整體的整合度很差。
JSONP 算是最常用的方法,利用 DOM 中的 Script 元素在載入遠端的 JavaScript,進而透過 Callback 回傳資料。這個方法由於是使用 HTTP GET Method 獲取 JS,因此若是要傳送資料到 Server 只能透過 URL 進行封裝 (我們都知道 URL 能傳遞的資料長度有限),很多現成的 Framework 都已經實作完整的 JSONP API。利用這樣的技術實作跨網域存取,可攜性相當高,幾乎所有的瀏覽器都可順利執行,但是單次可傳送的資料有限,且若是實作 WebService 就只符合瀏覽器進行呼叫,使得 Web Service 彈性變得很低。
W3C - CORS (Cross-Origin Resource Sharing) 這就是今天要介紹的重點,W3C 所制定的方式,目前主流的瀏覽器都有支援,著要透過 HTTP Header 來確認存取權限,算是一種寬鬆性的解決辦法。
如果瀏覽器有實作 CORS,那麼在發送跨網域 Request 之前會先發出一個 OPTION Request 來詢問目的網站是否接受跨網域的 Request,可以看一下之前測試的結果,如下圖:
上圖中 Google Chrome 果然發出了一個 HTTP Method 為 OPTION 的 Request,那麼要如何實作 CORS 呢?參考 W3C - Cross-Origin Resource Sharing 所制定的規範,我們先修改一下 hack.php 這個接收資料的程式,加入以下 Header,如下:
<?php // Cross-Origin Resource Sharing Header header('Access-Control-Allow-Origin: http://base.com'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: X-Requested-With, Content-Type, Accept'); ?>
這些 Headers 是要告訴瀏覽器自己允許何種類型的 HTTP Request 進行跨網域存取,其中定義了允許從哪個網域發 Request 過來 (Access-Control-Allow-Origin)、允許使用哪幾種 HTTP Methods (Access-Control-Allow-Methods)、允許夾帶哪些 Headers 等等 (GET, POST, PUT, DELETE, OPTIONS),其他更進階的用法可以參考文件。
修改好後再試一次,就可以發現 OPTION Request 收到回應後就立即發送原本要發送的 POST Request 了,如下圖:
最後我們重新整理一下整個事件發生的經過,參考下圖:
首先假設瀏覽器被要求發送一個 Request 到非目前瀏覽的網域(上述範例目前瀏覽的網域為 http://base.com,要傳送資料的跨網域為 http://hack.com),就會先利用 HTTP OPTION Method 詢問這個不安全的網域是否允許傳送?上圖 (1) 的動作。
若是這個 OPTION 沒有回應允許傳送的 CORS Headers,那麼原本要發送的 Request 就不會被傳送。若是回應了符合條件的 CORS Headers,瀏覽器就會發送跨網域 Request 如上圖 (2) 的動作。
討論
整體來看,從安全性考量這個方法其實有些危險,假設駭客在自己的 Web Site 實作了 CORS 那麼只要可以進行 XSS 那麼跨網域存取就簡單多了。
此外每次都用 OPTION 詢問有些浪費資源,其實可以透過 Access-Control-Max-Age 這個 Header 來指定規則的有效期限 (類似 Cookie 的機制),在這個有效時間內瀏覽器就會直接發送而不會一一詢問了,提昇了不少效率。
參考文件
11 comments for “實作 Cross-Origin Resource Sharing (CORS) 解決 Ajax 發送跨網域存取 Request”