JavaScript前端跨網域存取解決方案
1.前言
XMLHttpRequest cannot load ... Origin ... is not allowed by Access-Control-Allow-Origin
這個問題常發生在前端程式嘗試以 AJAX (XMLHttpRequest) 方式存取跨網域資源時,因為 Security 的考量,造成 request 發送失敗的情況。
Same Origin Policy
Same Origin Policy 是現代瀏覽器在安全性上的一個重要設計,主要是確保 script 只能在與其載入來源相同的頁面執行,達到不同 origin 網站彼此無法互相干擾。(註: 這裡指的相同 origin 是指 domain name, protocol, port 皆相同)。
對於 XMLHttpRequest 也有類似規範,當 client 端的 JS 對遠端主機發送 XMLHttpRequest 時,因為會挾帶瀏覽器所 maintain 的 cookie 資訊,因此只能對相同 origin 的 resource 進行存取(也就是 AJAX 呼叫的 url 必須與目前所在頁面相同),此限制確保了其存取的安全性。
2.目的
為什麼需要Cross-domain存取?
因為前端技術的快速發展成熟,使得前端與後端之間的分野愈來愈鮮明,前端負責頁面呈現與動態互動,後端負責演算與 API 服務介接,因此在開發這類型網站時,常會整合第三方的服務( e.g. flickr api 取得照片)或重覆利用自己先前開發的後端 API,但這就違反了 Browser 的 Same Origin Policy,因為不論是第三方或先前所開發的服務,通常都與網站位址不相同,所以處理跨網域資源存取的問題是必備的技能。
3.解決方案
AJAX Proxy
屬於 Server 端的解決方案,簡單來說就是將 web server 當成前端瀏覽器與其它第三方伺服器之間溝通的中介,browser 發送 AJAX request 給 server 端 proxy,proxy 再將 request 轉送給第三方服務並取得內容回傳給前端。通常有下列兩種實作策略:
(1) 前端完全不需知道使用什麼第三方服務,只需要存取後端 proxy 提供的方法,ajax proxy 會負責轉送 request 至第三方服務,取得結果並回傳給前端,讓前端有此服務就是後端 server 提供的感覺。
(2) 前端必須知道第三方服務的 url,並在發送 request 時以參數的方式傳遞到後端,後端僅提供一個介面將指定的 url 轉送並負責將結果回傳,這種方法讓開發者有瀏覽器沒有 policy 限制的感覺。
JSONP
JSONP = JSON with padding or prefix,第一次聽到JSONP時,可能會對它產生誤解,以為是另一種資料格式,但其實JSONP是一種避開Same Origin Policy發送 cross-domain request 的技巧。
其運作原理是利用script tag的open policy,意思就是browser對於載入任何網域的js native code沒有限制,藉由這個特性將所需的json data以function call包起來,而這個function是被execution context所定義好的,當js code被以script element的方式 inject到html時,就可以順利將後端資料帶入處理函式,達到跨網域請求資料的目的。
幸虧有了jQuery,使用jsonp將不再這麼麻煩,要發送跨域請求時只需要在$.ajax()代入參數dataType="jsonp",並在url加入參數callback=xxxx即可,當然後端也必須作相對應的配合(將資料以傳入的callback參數以function call型式包裝起來),回傳後jQuery會自動將response data傳給success callback,省去自定義callback步驟,如下所示:
JS
$.ajax({ |
|
url:"http://api.mydomain/jsonp.php?callback=test", |
|
dataType:"jsonp", |
|
success:function(response){ |
|
console.log(response); |
|
}, |
|
error:function(response){ |
|
console.log("error"); |
|
} |
|
}); |
PHP
<?php |
|
header("Content-Type: application/json"); |
|
echo$_GET["callback"]."(".json_encode( array("email"=>" Email住址會使用灌水程式保護機制。你需要啟動Javascript才能觀看它 ","auth_token"=>"cdefgh")).")"; |
|
?> |
CORS
CORS (Cross-Origin Resource Sharing) 是 W3C 針對不同源的 Browser 跟 Server 之間進行構通所制訂的 protocol,其實作原理就是透過兩個 Custom http header (Origin and Access-Control-Allow-Origin) 來確認雙方是否可以成功發送 Request 並回傳,client 端在發送 request 時 (僅限支援 CORS 的 Browser),如果發現請求目標非同一 domain,會自動加入 custom header: Origin 並傳入自己的 domain,而當 Server 接收請求並回傳時,必須加上 "Access-Control-Allow-Origin: http://domain/allowed" 這個 header,帶入的值就是允許跨域請求的白名單,如果是星號 (*) 代表允許全部,如果有安全性考量要避免使用。
上述解法僅限 Simple Request,何謂 Simple request 在 W3C 官網有明確的規範,只要 Client 發送的請求符合下列條件都算是簡單的請求:
HTTP method:
Head
Get
Post
HTTP headers:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type: application/x-www-form-urlencode
Content-Type: multipart/form-data
Content-Type: text/plain
以一個簡單的例子示範 CORS 用法:
JS (只需要簡單發送一個跨域的AJAX請求)
//assume origin domain = http://www.mydomain |
|
$.ajax({ |
|
url:"http://api.mydomain/api.php", |
|
success:function(response){ |
|
console.log(response); |
|
} |
|
}); |
PHP (回傳允許跨域存取的HEADER)
<?php |
|
header("Access-Control-Allow-Origin: http://www.mydomain"); |
|
header("Content-Type: application/json"); |
|
echojson_encode(array("msg"=>"CORS is awesome!")); |
|
?> |
4.優缺點
上述三種方法都可以用來解決xhrcross domain access的問題,但不同解法有不同限制,像是jsonp與cors需要response端的配合,如果是第三方服務且沒有支援這兩種方法的情況可能使用proxy才可能解決問題,或是考慮服務所支援的瀏覽器是否皆有實作cors…等等,都是採用哪種方法所要考量的。
5.參考來源
•http://www.ruanyifeng.com/blog/2016/04/cors.html
•http://ericachang.github.io/2013/07/01/jsonp_and_cors/