json

JavaScript前端跨網域存取解決方案

詹國忠 2016/11/10 11:32:51
3983







主題

JavaScript前端跨網域存取解決方案

文章簡介

介紹JavaScript跨網域存取的方法及優缺點比較

作者

詹國忠

版本/產出日期

V1.0/2016.11.09




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"=>"nono@gmail.com","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");



echo json_encode(array("msg"=>"CORS is awesome!"));


?>





4. 優缺點

上述三種方法都可以用來解決 xhr cross 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/

詹國忠