最常用的兩種跨域解決方案

同源策略:

是由NetScape提出的著名的安全策略,所有支持javaScript的瀏覽器都使用這個(gè)策略。同源策略限制了一個(gè)源中加載文本或腳本與來(lái)自其它源中資源的交互方式。

IE特例:

  • 授信范圍(Trust Zones):兩個(gè)相互之間高度互信的域名,如公司域名(corporate domains),不遵守同源策略的限制。

  • 端口:IE未將端口號(hào)加入到同源策略的組成部分之中,因此 http://company.com:81/index.htmlhttp://company.com/index.html 屬于同源并且不受任何限制。

以上參考自MDN

所謂同源是指:域名、協(xié)議、端口都相同。同源策略是瀏覽器最核心也最基本的安全功能
這是知乎上關(guān)于同源策略的一篇文章。。但是瀏覽器在安全性和實(shí)用性上做出了讓步,img/script/style/iframe等有src屬性的都可以跨域引用資源。

方案一——通過(guò)JSONP跨域

jsonp是JSON with padding的簡(jiǎn)寫(xiě),看起來(lái)與json差不多,但是包含在函數(shù)調(diào)用中的json,利用動(dòng)態(tài)script元素來(lái)使用(具有src屬性的如img,iframe,srcipt都不受同源策略的影響)。該協(xié)議的一個(gè)要點(diǎn)就是允許用戶傳遞一個(gè)callback參數(shù)給服務(wù)端,然后服務(wù)端返回?cái)?shù)據(jù)時(shí)會(huì)將這個(gè)callback參數(shù)作為函數(shù)名來(lái)包裹住JSON數(shù)據(jù),這樣客戶端就可以隨意定制自己的函數(shù)來(lái)自動(dòng)處理返回?cái)?shù)據(jù)了。

如果使用jquery,可以在type為get的時(shí)候dataType設(shè)為jsonp,就可以了。

$.ajax({
        url: 'http://www.qdaily.com/get_user_and_radar.json?winWidth=1280&winHeight=800',
        type: 'get',
        dataType: 'jsonp',
        data: {},
    })
    .done(function(data) {
        console.log(data.status);
    })

然后,很幸運(yùn)的就可以看到這個(gè):

Paste_Image.png

但是可以看出其實(shí)是請(qǐng)求到數(shù)據(jù)的,只是沒(méi)有執(zhí)行我們的callBack函數(shù)。

Paste_Image.png

因?yàn)榍懊嬗刑岬剑悍?wù)端返回?cái)?shù)據(jù)時(shí)會(huì)將這個(gè)callback參數(shù)作為函數(shù)名來(lái)包裹住JSON數(shù)據(jù),也就是服務(wù)端的返回需要拼接成這樣(比如函數(shù)名叫做haddleData):

haddleData({"status":true,subscribes:["12","23"]})

另外:jquery的ajax方法可以傳入兩個(gè)參數(shù)

jsonp : "callback"http://設(shè)置這個(gè)會(huì)替換瀏覽器發(fā)送請(qǐng)求時(shí)地址后面自動(dòng)添加的?callback=xxx中的callback這個(gè)字,一般情況下不用傳這個(gè)參數(shù)
jsonpCallback: "success_jsonpCallback"http://這個(gè)值將用來(lái)取代jQuery自動(dòng)生成的隨機(jī)函數(shù)名,也就是上句話中的'xxx'。

【這兒遺留了一個(gè)問(wèn)題,還請(qǐng)各位解決】如果jsonpCallback不手動(dòng)設(shè)置的話,jquery是會(huì)自動(dòng)生成個(gè)隨機(jī)的字符串的,但是服務(wù)器那邊的函數(shù)名要寫(xiě)什么,回調(diào)函數(shù)才會(huì)正確的執(zhí)行呢?

看一個(gè)運(yùn)行成功的實(shí)例:

$.ajax({
        url: 'http://localhost:8000/remote/remote.js',
        type: 'get',
        dataType: 'jsonp',
        jsonp: 'back',
        jsonpCallback: "haddleData",
        data: {},
    })
    .done(function(data) {
        console.log(data.status);
    })
//這段程序運(yùn)行在8080端口的

remote.js作為遠(yuǎn)端其中寫(xiě)了句

haddleData({"status":true,subscribes:["12","23"]})
Paste_Image.png

對(duì)于大部分js初學(xué)者,大多使用jquery,而對(duì)原生不太熟悉。但是須知,ajax和jsonp其實(shí)本質(zhì)上是不同的東西,ajax的核心是通過(guò)XmlHttpRequest獲取非本頁(yè)內(nèi)容,而jsonp的核心則是動(dòng)態(tài)添加<script>標(biāo)簽來(lái)調(diào)用服務(wù)器提供的js腳本,利用腳本下載下來(lái)后立即執(zhí)行的事實(shí)實(shí)現(xiàn)callback方法的調(diào)用。

原生js的jsonp簡(jiǎn)單實(shí)現(xiàn):

<script>
    function haddleData(data){
        console.log(data.status);
    }
</script>
<script type="text/javascript" src="http://localhost:8000/remote/remote.js"></script>

當(dāng)然有多個(gè)本地函數(shù)需要處理的時(shí)候,加上回調(diào)函數(shù)名才是方便的,像這樣:

<script type="text/javascript" src="http://localhost:8000/remote/remote.js?callback=haddleData"></script>

加上callback參數(shù),可以使得服務(wù)器端根據(jù)前端指定的方法名cb動(dòng)態(tài)返回cb(data);而不是都寫(xiě)死handleData(data);

【總結(jié)下jsonp】:

  • 優(yōu)點(diǎn):
    簡(jiǎn)單,函數(shù)回調(diào)在本地處理;
  • 缺點(diǎn):
    1、安全性(存在注入漏洞,如CSRF,XSS);
    2、如果出現(xiàn)錯(cuò)誤,不會(huì)像http請(qǐng)求那樣有狀態(tài)碼;
    3、只能使用get請(qǐng)求;

方案二——CORS(Cross-Origin Resource Sharing)

這是一個(gè)W3C標(biāo)準(zhǔn)(顯然比jsonp背景深厚許多),同樣需要瀏覽器和服務(wù)器同時(shí)支持,但是整個(gè)通信過(guò)程,都是瀏覽器自動(dòng)完成,不需要用戶參與,就像平時(shí)寫(xiě)Ajax一樣(如果使用的是jquery的話)。
下面是原生js實(shí)現(xiàn)CORS的代碼

    function createCORSRequest(method,url){
        var xhr=new XMLHttpRequest();
        if("withCredentials" in xhr){
            xhr.open(method,url,true);
        }else if(typeof XDomainRequest != "undefined"){//IE10之前的版本使用XDmainRequest支持CORS
            xhr=new XDomainRequest();
            xhr.open(method,url);
        }else{
            xhr=null;
        }
        return xhr;
    }
    var request=createCORSRequest("get","待訪問(wèn)的地址");
    if(request){
        request.onload=function(data){
            //do sth
        };
        request.send();
    }   

適用場(chǎng)景:

承載的信息量大,get形式搞不定,需選用post傳輸。CORS支持所有類型的傳輸。

兼容性:

移動(dòng)端全面支持(除opera mini),PC上IE8+。

CORS思想:

使用自定義的HTTP頭部讓瀏覽器與服務(wù)器進(jìn)行溝通,從而決定請(qǐng)求或響應(yīng)是應(yīng)該成功還是失敗。(請(qǐng)求和響應(yīng)都不包含cookie)

當(dāng)然如果設(shè)置成下面這樣,所有的跨域都可以實(shí)現(xiàn)了,但這樣畢竟太不安全。
"Access-Control-Allow-Origin:*";//允許任何域向我們的服務(wù)器發(fā)送請(qǐng)求

一般情況下,瀏覽器發(fā)送一個(gè)額外的Origin頭部(由瀏覽器自動(dòng)生成發(fā)送)

Origin:http://localhost:8080//本地網(wǎng)址

然后由服務(wù)器發(fā)送一個(gè)響應(yīng)表頭:Access-Control-Allow-Origin,如果服務(wù)器接收該請(qǐng)求,返回值(只能是通配符或單域名。)就和請(qǐng)求值一樣。

Access-Control-Allow-Origin:http://localhost:8080

=>CORS方案的重點(diǎn)其實(shí)就在于服務(wù)器端的配置。

簡(jiǎn)單請(qǐng)求

  • 只使用 GET, HEAD 或者 POST 請(qǐng)求方法。

如果使用 POST 向服務(wù)器端傳送數(shù)據(jù),則數(shù)據(jù)類型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一種。

  • 不會(huì)使用自定義請(qǐng)求頭(類似于 X-Modified 這種)。

HTTP頭部信息不超出以下{Accept,Accept-Language,Content-Language,Last-Event-ID,content-type(只限于上面提到的3種類型)}

對(duì)于簡(jiǎn)單請(qǐng)求,瀏覽器直接發(fā)出CORS請(qǐng)求。瀏覽器會(huì)自動(dòng)在頭信息(Request Headers)中,添加一個(gè)Origin 字段,來(lái)表明本次請(qǐng)求來(lái)自哪個(gè)域。

Paste_Image.png

如果這個(gè)源不在許可范圍內(nèi),會(huì)報(bào)錯(cuò): No 'Access-Control-Allow-Origin' header is present on the requested resource.

如果Origin指定的域名在許可范圍內(nèi)(必須是跨域了的),Response Headers中會(huì)多出幾個(gè)頭信息字段。

Access-Control-Allow-Credentials:true//值為true表示允許發(fā)送cookie
Access-Control-Allow-Methods:GET, POST, OPTIONS
Access-Control-Allow-Origin:http://localhost:8080
Access-Control-Max-Age:1728000
withCredentials屬性

因?yàn)镃ORS默認(rèn)不發(fā)送cookie和http認(rèn)證,如果要把Cookie發(fā)到服務(wù)器,就要指定Access-Control-Allow-Credentials:true;
另外AJAX中也要打開(kāi)withCredentials屬性。

var xhr=new XMLHttpRequest();
xhr.withCredentials=true;

jquery ajax請(qǐng)求參數(shù)中加入

xhrFields: {
  withCredentials: true
}

非簡(jiǎn)單請(qǐng)求

除了上面說(shuō)的簡(jiǎn)單請(qǐng)求外都是非簡(jiǎn)單請(qǐng)求,比如:請(qǐng)求方法是PUT
或DELETE,或者Content-Type字段的類型是application/json,又或者有自定義請(qǐng)求頭Access-Control-Request-Headers: X-Custom-Header。

比如,我添加自定義請(qǐng)求頭

xhr.setRequestHeader('Some-Custom-Response-Header', 'value');

就會(huì)發(fā)現(xiàn)連續(xù)向同一地址請(qǐng)求了兩次,而第一次請(qǐng)求什么值也沒(méi)拿到

Paste_Image.png

第二次請(qǐng)求

Paste_Image.png

這是因?yàn)闉g覽器發(fā)現(xiàn),這是一個(gè)非簡(jiǎn)單請(qǐng)求,就自動(dòng)發(fā)出一個(gè)"預(yù)檢"請(qǐng)求,要求服務(wù)器確認(rèn)可以這樣請(qǐng)求。"預(yù)檢"請(qǐng)求用的請(qǐng)求方法是OPTIONS,表示這個(gè)請(qǐng)求是用來(lái)詢問(wèn)的。"預(yù)檢"請(qǐng)求之后,瀏覽器球會(huì)進(jìn)行正常CORS請(qǐng)求。

服務(wù)器端配置(rails為例)

1、可以參考這個(gè)方法:為 RESTful API 配置 CORS 實(shí)現(xiàn)跨域請(qǐng)求,寫(xiě)的挺詳細(xì)的。然而對(duì)于rails并不算熟悉的我來(lái)說(shuō),有g(shù)em包,怎么可能放著不用呢~
2、這是Rack CORS 中間件的項(xiàng)目地址,按照文檔的說(shuō)明1分鐘就可以基本搞定。

【問(wèn)題:】

當(dāng)我請(qǐng)求方式為put時(shí),卻出現(xiàn)了404錯(cuò)誤,是因?yàn)闆](méi)有為options提供路由么?

rails中使用CORS中的功能和細(xì)節(jié)還有很多,等到需要時(shí)再行挖掘吧,歡迎使用過(guò)的各位一起交流問(wèn)題。

【參考】

http://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=402804565&idx=1&sn=415fc9eab30edcb66227ed5e099f9a66&scene=1&srcid=0413psVAfbsElTaWvrVas6xM#rd
http://www.ruanyifeng.com/blog/2016/04/cors.html
http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
http://www.cnblogs.com/know/archive/2011/10/09/2204005.html
http://blog.csdn.net/xiaoxian8023/article/details/27817861

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容