跨域问题的前后端解决方案
跨域問題是開發過程中一個比較常見的問題,無論你是前臺開發,還是后臺開發,可能都處理過這個問題。本文主要是介紹跨域常用的解決方案。
什么是跨域?
假設有這么一個場景,我有一個網站,在里面有一個顯示商品的功能,對應的頁面地址是:
http://www.myexample.com/page/page-a.html
在實現這個頁面時,我通過iframe集成了另外一個網站的商品展示功能,對應的頁面地址是:
http://www.othersite.com/page/show.html
頁面看起來可能是這樣的,我簡化了所有的內容,通過不同的背景色來區分不同的頁面。
可以想象,我并沒有做太多的開發,就擁有一個商品展示功能了。但是在page-a頁面中,并不能通過Javascript來訪問show頁面的document、cookie等對象,不能修改show頁面中的任何內容。代碼看起來是這樣的,運行的時候會產生錯誤:
document.getElementById("iframe").contentWindow.document;
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
為什么這樣設計?
我們反過來思考,如果可以訪問document、cookie,會出現什么問題?
可以通過Javascript來監聽show頁面上的輸入框,可以改變表單提交的URL。在myexample這個網站就可以做任何事情了,截獲用戶的敏感數據了,比如登錄信息、個人喜好等數據。結果就是,在你訪問一個網站的時候,你的數據很容易就被泄露了,包括用戶名和密碼。
所以,簡單地說就是安全,在WWW創立之初,設計者就考慮到了這個問題,通過一些策略來保證用戶信息的安全,防止惡意的網站竊取數據。
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.
這個叫同源策略(wiki),所有的瀏覽器都支持這個安全策略。從上面的定義來看,同源指的是三個相同:
1、協議(scheme)相同
 2、域名(hostname)相同
 3、端口(port)相同
這里又要引入一個新的概念:
URI(wiki)
Uniform Resource Identifier (URI) is a string of characters used to identify a resource.
具體的語法如下圖所示:
 ?
同源概念中的scheme、host、port對應到上圖中的結構,回到我們剛才的例子:
http://www.myexample.com/page/page.html
 1、scheme是http
 2、host是www.myexample.com
 3、port沒有表示是80
這樣就很容易理解了,跨域就是不同源,兩個不同源的網站相互會被限制,限制有三種:
1、Cookie、LocalStorage 和 IndexDB 無法讀取。
 2、DOM無法獲得。
 3、AJAX請求不能發送。
前端解決方案
1、修改Domain
瀏覽器允許通過設置document.domain共享 Cookie,相當于是把兩個不同源的頁面設置成相同的源,這種方法只適用于 Cookie 和 iframe 窗口,而且要求兩個網頁一級域名相同,只是二級域名不同。
document.domain='example.com';
2、window通信
這個方案簡單的說,就是通過DOM的window對象來傳遞參數。阮一峰老師總結了三點,非常清晰,我就不重復的去寫了:
片段標識符(fragment identifier)
 window.name
 跨文檔通信 API(Cross-document messaging)
詳情見:參考資料4
3、JSONP
在一個頁面中,可以使用<script>標記來引用一個外部的JS文件,并且能夠成功執行。直接上代碼可能會更好理解一些。
假設我在某個網站有一個js文件,URL是:http://remoteserver.com/remote.js,里面的代碼很簡單:
alert('this is a remote alert!');
在另外一個網站的頁面http://localserver.com/page.html,可以引入這個JS文件:
<head>
 ? ? <script type="text/javascript" src="http://remoteserver.com/remote.js"/>
 </head>
在瀏覽器中打開page.html,會彈出一個警告對話框。
JSONP就是利用了這一點,如果將alert的內容改為下面的內容:
localMethod({"result":"data from remote!"});
同時將page.html的內容修改一下
<script type="text/javascript">
 ? ? function localMethod(data){
 ? ? ? ? alert(data.result);
 ? ? }
 </script>
 <script type="text/javascript" src="http://remoteserver.com/remote.js">
再次運行,alert的內容就是:data from remote!
而script元素是可以通過document.createElement('script')動態創建的,也就具備了隨時可以引入一個外部script,這樣就達到了跨域訪問的目的,但是JSONP只支持GET請求,其他的方式不支持。
4、WebSocket
WebSocket是一種通信協議,不實行同源政策,詳情見參考資料4。
上面的幾種方法,可以說是奇技淫巧,繞過了瀏覽器的限制。隨著前端框架的興起,以及前后端分離架構的流行,上面的技巧已經比較陳舊了。
后端解決方案
1、URL轉發
在同一個窗口中,通過URL提交的方式,多次跳轉,需要兩邊的頁面相互支持。
比如在http://www.myexample.com/page/page-a.html頁面中,有一個提交按鈕,將數據post到遠端的服務器,需要告訴對方跳轉回來到哪個頁面,看起來像這樣:
http://www.othersite.com/page/show.html?url=http://www.myexample.com/page/page-b.html
遠程服務器show.html處理完成后,再次將數據提交到page-b.html,page-a和page-b是在同一個域下面的,所以就可以相互訪問了。
2、HTTP代理
原理就是把遠程服務通過代理服務器變成本地的服務,需要借助WEB服務器,Nginx和Apache都支持代理轉發。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,非常詳細,這里補充一個交互示意圖,幫助理解。
以上這些就是跨域的常見解決方案,其中CORS是終極解決方案,可以適用于多種場景。
?
參考資料:
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
 作者:大家叫我杰哥
 鏈接:https://www.jianshu.com/p/fccabaf38ac2
 來源:簡書
 簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
總結
以上是生活随笔為你收集整理的跨域问题的前后端解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 用 golang 1.11 module
- 下一篇: 现代环境下的网络分割
