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的問題,但不同解法有不同限制,像是jsonpcors需要response端的配合,如果是第三方服務且沒有支援這兩種方法的情況可能使用proxy才可能解決問題,或是考慮服務所支援的瀏覽器是否皆有實作cors等等,都是採用哪種方法所要考量的。

 

 

5.參考來源

http://www.ruanyifeng.com/blog/2016/04/cors.html

http://ericachang.github.io/2013/07/01/jsonp_and_cors/

 

原文 https://tpu.thinkpower.com.tw/tpu/articleDetails/402?fbclid=IwAR10ZdTZuGNCegoirm10DZyhhRQqI-6rktb9QnlbFv9TMfMHEBFPMXruB_k