跨域問題的前后端解決方案

跨域問題是開發(fā)過程中一個(gè)比較常見的問題,無論你是前臺(tái)開發(fā),還是后臺(tái)開發(fā),可能都處理過這個(gè)問題。本文主要是介紹跨域常用的解決方案。

什么是跨域?

假設(shè)有這么一個(gè)場(chǎng)景,我有一個(gè)網(wǎng)站,在里面有一個(gè)顯示商品的功能,對(duì)應(yīng)的頁面地址是:

http://www.myexample.com/page/page-a.html

在實(shí)現(xiàn)這個(gè)頁面時(shí),我通過iframe集成了另外一個(gè)網(wǎng)站的商品展示功能,對(duì)應(yīng)的頁面地址是:

http://www.othersite.com/page/show.html

頁面看起來可能是這樣的,我簡(jiǎn)化了所有的內(nèi)容,通過不同的背景色來區(qū)分不同的頁面。

可以想象,我并沒有做太多的開發(fā),就擁有一個(gè)商品展示功能了。但是在page-a頁面中,并不能通過Javascript來訪問show頁面的document、cookie等對(duì)象,不能修改show頁面中的任何內(nèi)容。代碼看起來是這樣的,運(yùn)行的時(shí)候會(huì)產(chǎn)生錯(cuò)誤:

document.getElementById("iframe").contentWindow.document;

// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.

為什么這樣設(shè)計(jì)?

我們反過來思考,如果可以訪問document、cookie,會(huì)出現(xiàn)什么問題?

可以通過Javascript來監(jiān)聽show頁面上的輸入框,可以改變表單提交的URL。在myexample這個(gè)網(wǎng)站就可以做任何事情了,截獲用戶的敏感數(shù)據(jù)了,比如登錄信息、個(gè)人喜好等數(shù)據(jù)。結(jié)果就是,在你訪問一個(gè)網(wǎng)站的時(shí)候,你的數(shù)據(jù)很容易就被泄露了,包括用戶名和密碼。

所以,簡(jiǎn)單地說就是安全,在WWW創(chuàng)立之初,設(shè)計(jì)者就考慮到了這個(gè)問題,通過一些策略來保證用戶信息的安全,防止惡意的網(wǎng)站竊取數(shù)據(jù)。

A web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number.

這個(gè)叫同源策略(wiki),所有的瀏覽器都支持這個(gè)安全策略。從上面的定義來看,同源指的是三個(gè)相同:
1、協(xié)議(scheme)相同
2、域名(hostname)相同
3、端口(port)相同

這里又要引入一個(gè)新的概念:

URI(wiki

Uniform Resource Identifier (URI) is a string of characters used to identify a resource.

具體的語法如下圖所示:

同源概念中的scheme、host、port對(duì)應(yīng)到上圖中的結(jié)構(gòu),回到我們剛才的例子:

http://www.myexample.com/page/page.html
1、scheme是http
2、host是www.myexample.com
3、port沒有表示是80

這樣就很容易理解了,跨域就是不同源,兩個(gè)不同源的網(wǎng)站相互會(huì)被限制,限制有三種:

1、Cookie、LocalStorage 和 IndexDB 無法讀取。
2、DOM無法獲得。
3、AJAX請(qǐng)求不能發(fā)送。

前端解決方案

1、修改Domain

瀏覽器允許通過設(shè)置document.domain共享 Cookie,相當(dāng)于是把兩個(gè)不同源的頁面設(shè)置成相同的源,這種方法只適用于 Cookie 和 iframe 窗口,而且要求兩個(gè)網(wǎng)頁一級(jí)域名相同,只是二級(jí)域名不同。

document.domain='example.com';

2、window通信

這個(gè)方案簡(jiǎn)單的說,就是通過DOM的window對(duì)象來傳遞參數(shù)。阮一峰老師總結(jié)了三點(diǎn),非常清晰,我就不重復(fù)的去寫了:

片段標(biāo)識(shí)符(fragment identifier)
window.name
跨文檔通信 API(Cross-document messaging)

詳情見:參考資料4

3、JSONP

在一個(gè)頁面中,可以使用<script>標(biāo)記來引用一個(gè)外部的JS文件,并且能夠成功執(zhí)行。直接上代碼可能會(huì)更好理解一些。

假設(shè)我在某個(gè)網(wǎng)站有一個(gè)js文件,URL是:http://remoteserver.com/remote.js,里面的代碼很簡(jiǎn)單:

alert('this is a remote alert!');

在另外一個(gè)網(wǎng)站的頁面http://localserver.com/page.html,可以引入這個(gè)JS文件:

<head>
? ? <script type="text/javascript" src="http://remoteserver.com/remote.js"/>
</head>

在瀏覽器中打開page.html,會(huì)彈出一個(gè)警告對(duì)話框。

JSONP就是利用了這一點(diǎn),如果將alert的內(nèi)容改為下面的內(nèi)容:

localMethod({"result":"data from remote!"});

同時(shí)將page.html的內(nèi)容修改一下

<script type="text/javascript">
? ? function localMethod(data){
? ? ? ? alert(data.result);
? ? }
</script>
<script type="text/javascript" src="http://remoteserver.com/remote.js">

再次運(yùn)行,alert的內(nèi)容就是:data from remote!

而script元素是可以通過document.createElement('script')動(dòng)態(tài)創(chuàng)建的,也就具備了隨時(shí)可以引入一個(gè)外部script,這樣就達(dá)到了跨域訪問的目的,但是JSONP只支持GET請(qǐng)求,其他的方式不支持。

4、WebSocket

WebSocket是一種通信協(xié)議,不實(shí)行同源政策,詳情見參考資料4。

上面的幾種方法,可以說是奇技淫巧,繞過了瀏覽器的限制。隨著前端框架的興起,以及前后端分離架構(gòu)的流行,上面的技巧已經(jīng)比較陳舊了。

后端解決方案

1、URL轉(zhuǎn)發(fā)

在同一個(gè)窗口中,通過URL提交的方式,多次跳轉(zhuǎn),需要兩邊的頁面相互支持。

比如在http://www.myexample.com/page/page-a.html頁面中,有一個(gè)提交按鈕,將數(shù)據(jù)post到遠(yuǎn)端的服務(wù)器,需要告訴對(duì)方跳轉(zhuǎn)回來到哪個(gè)頁面,看起來像這樣:

http://www.othersite.com/page/show.html?url=http://www.myexample.com/page/page-b.html

遠(yuǎn)程服務(wù)器show.html處理完成后,再次將數(shù)據(jù)提交到page-b.html,page-a和page-b是在同一個(gè)域下面的,所以就可以相互訪問了。

2、HTTP代理

原理就是把遠(yuǎn)程服務(wù)通過代理服務(wù)器變成本地的服務(wù),需要借助WEB服務(wù)器,Nginx和Apache都支持代理轉(zhuǎn)發(fā)。Nginx的配置參考:

location /api/proxy {
? ? proxy_pass? http://remoteipaddress:8080/api;
? ? proxy_set_header? Host? $host;
? ? proxy_set_header? X-Real-IP? $remote_addr;
? ? proxy_set_header? X-Forwarded-For? $proxy_add_x_forwarded_for;
}

Node也有開源的組件http-proxy-middleware可以支持代理。

var express=require('express');
var proxy=require('http-proxy-middleware');
var app=express();
app.use('/api',proxy({target:'http://www.example.org', changeOrigin:true}));
app.listen(3000);


3、CORS

詳情見參考資料5,非常詳細(xì),這里補(bǔ)充一個(gè)交互示意圖,幫助理解。

以上這些就是跨域的常見解決方案,其中CORS是終極解決方案,可以適用于多種場(chǎng)景。


參考資料:

1.https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
2.https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
3.https://www.w3.org/TR/cors/
4.http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
5.http://www.ruanyifeng.com/blog/2016/04/cors.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是跨域? 2.) 資源嵌入:、、、等dom標(biāo)簽,還有樣式中background:url()、@font-fac...
    電影里的夢(mèng)i閱讀 2,462評(píng)論 0 5
  • 1. 什么是跨域? 跨域一詞從字面意思看,就是跨域名嘛,但實(shí)際上跨域的范圍絕對(duì)不止那么狹隘。具體概念如下:只要協(xié)議...
    他在發(fā)呆閱讀 858評(píng)論 0 0
  • 為什么要讓孩子堅(jiān)持練跆拳道? 堅(jiān)持的理由有很多,最簡(jiǎn)單最重要的一條就是:“人的成功與智力沒有太大的關(guān)系,也并不是誰...
    清香_6420閱讀 2,102評(píng)論 0 0
  • 今天這本書,是我的準(zhǔn)備買的書的清單。然后今天又看到他在晨讀里面出現(xiàn)了。 晨讀里面的都是一些比較簡(jiǎn)練的內(nèi)容,目的是將...
    亢奮的蘑菇閱讀 225評(píng)論 0 4
  • 很榮幸參加了拆書幫紫禁之顛分舵第一期RIA便簽訓(xùn)練營,到目前為止,課程還未結(jié)束,但已收獲滿滿。這必須感謝三級(jí)拆書家...
    墨竹_sunshine閱讀 1,018評(píng)論 0 8

友情鏈接更多精彩內(nèi)容