同源策略和跨域訪問的解決方案

來源:互聯網
上載者:User
跨域是一個經常會遇到的問題,特別是有兩個網域名稱的時候,如你的頁面在a.test.com,然後需要向b.test.com請求資料,這個時候就跨域了。如果直接請求,你會發現瀏覽器就報錯了,這個時候,你就需要瞭解什麼是同源策略。

1、什麼是同源策略。


理解跨域首先必須要瞭解同源策略。同源策略是瀏覽器上為安全性考慮實施的非常重要的安全性原則。
 何謂同源。
 URL由協議、網域名稱、連接埠和路徑組成,如果兩個URL的協議、網域名稱和連接埠相同,則表示他們同源。
 同源策略:瀏覽器的同源策略,限制了來自不同源的"document"或指令碼,對當前"document"讀取或設定某些屬性。 (白帽子講web安全[1]): 從一個域上載入的指令碼不允許訪問另外一個域的文件屬性。
舉個例子:
 比如,一個惡意網站的頁面通過iframe嵌入了銀行的登入頁面(二者不同源),如果沒有同源限制,惡意網頁上的javascript指令碼就可以在使用者登入銀行的時候擷取使用者名稱和密碼。

在瀏覽器中,<script>、<img>、<iframe>、<link>等標籤都可以載入跨域資源,而不受同源限制,但瀏覽器限制了JavaScript的許可權使其不能讀、寫載入的內容。
 另外同源策略只對網頁的HTML文檔做了限制,對載入的其他靜態資源如javascript、css、圖片等仍然認為屬於同源。

 程式碼範例(http://localhost:8080/和http://localhost:8081由於連接埠不同而不同源):

http://localhost:8080/test.html  

        <html>  

            <head><title>test same origin policy</title></head>  

            <body>  

                <iframe id="test" src="http://localhost:8081/test2.html"></iframe>  

                <script type="text/javascript">  

                    document.getElementById("test").contentDocument.body.innerHTML = "write somthing";  

                </script>  

            </body>  

        </html>  

  

http://localhost:8081/test2.html  

        <html>  

            <head><title>test same origin policy</title></head>  

            <body>  

                Testing.  

            </body>  

        </html>  

在Firefox中會得到如下錯誤:
 Error: Permission denied to access property 'body'

Document對象的domain屬性存放著裝載文檔的伺服器的主機名稱,可以設定它。
例如來自"blog.csdn.net"和來自"bbs.csdn.net"的頁面,都將document.domain設定為"csdn.net",則來自兩個子網域名稱的指令碼即可相互訪問。
 出於安全的考慮,不能設定為其他主domain,比如http://www.csdn.net/不能設定為sina.com。


2、Ajax跨域


  Ajax (XMLHttpRequest)請求受到同源策略的限制。

 Ajax通過XMLHttpRequest能夠與遠端伺服器進行資訊互動,另外XMLHttpRequest是一個純粹的Javascript對象,這樣的互動過程,是在後台進行的,使用者不易察覺。
因此,XMLHTTP實際上已經突破了原有的Javascript的安全限制。

 舉個例子:
假設某網站引用了其它網站的javascript,這個網站被compromise並在javascript中加入擷取使用者輸入並通過ajax提交給其他網站,這樣就可以源源不斷收集資訊。
或者某網站因為存在漏洞導致XSS注入了javascript指令碼,這個指令碼就可以通過ajax擷取使用者資訊並通過ajax提交給其他網站,這樣就可以源源不斷收集資訊。


如果我們又想利用XMLHTTP的無重新整理非同步互動能力,又不願意公然突破Javascript的安全性原則,可以選擇的方案就是給XMLHTTP加上嚴格的同源限制。

 這樣的安全性原則,很類似於Applet的安全性原則。IFrame的限制還僅僅是不能訪問跨域HTMLDOM中的資料,而XMLHTTP則根本上限制了跨域請求的提交。(實際上下面提到了CORS已經放寬了限制)

隨著Ajax技術和網路服務的發展,對跨域的要求也越來越強烈。下面介紹Ajax的跨域技術。

01 2.1、JSONP

 JSONP技術實際和Ajax沒有關係。我們知道<script>標籤可以載入跨域的javascript指令碼,並且被載入的指令碼和當前文檔屬於同一個域。


因此在文檔中可以調用/訪問指令碼中的資料和函數。如果javascript指令碼中的資料是動態產生的,那麼只要在文檔中動態建立<script>標籤就可以實現和服務端的資料互動。


JSONP就是利用<script>標籤的跨域能力實現跨域資料的訪問,請求動態產生的JavaScript指令碼同時帶一個callback函數名作為參數。


其中callback函數本地文檔的JavaScript函數,伺服器端動態產生的指令碼會產生資料,並在代碼中以產生的資料為參數調用callback函數。


當這段指令碼載入到本地文檔時,callback函數就被調用。

第一個網站的測試頁面(http://localhost:8080/test.html):  


<script src="http://localhost:8081/test_data.js">  

    <script>  

        function test_handler(data) {  

            console.log(data);  

        }  

</script>  


伺服器端的Javascript指令碼(http://localhost:8081/test_data.js):
test_handler('{"data": "something"}');

為了動態實現JSONP請求,可以使用Javascript動態插入<script>標籤:


<script type="text/javascript">  

        // this shows dynamic script insertion  

        var script = document.createElement('script');  

        script.setAttribute('src', url);  

        // load the script  

        document.getElementsByTagName('head')[0].appendChild(script);   

</script>  


JSONP協議封裝了上述步驟,jQuery中統一是現在AJAX中(其中data type為JSONP):http://localhost:8080/test?callback=test_handler
    
 為了支援JSONP協議,伺服器端必須提供特別的支援[2],另外JSONP只支援GET請求。

02
 2.2、Proxy 

使用代理方式跨域更加直接,因為SOP的限制是瀏覽器實現的。如果請求不是從瀏覽器發起的,就不存在跨域問題了。
使用本方法跨域步驟如下:
1. 把訪問其它域的請求替換為本域的請求
2. 本域的請求是伺服器端的動態指令碼負責轉寄實際的請求
各種伺服器的Reverse Proxy功能都可以非常方便的實現請求的轉寄,如Apache httpd + mod_proxy。
Eg.

為了通過Ajax從http://localhost:8080訪問http://localhost:8081/api,可以將請求發往http://localhost:8080/api。
然後利用Apache Web伺服器的Reverse Proxy功能做如下配置:
ProxyPass /api http://localhost:8081/api



03

2.3、CORS 2.3.1 Cross origin resource sharing

“Cross-origin resource sharing (CORS) is a mechanism that allows a web page to make XMLHttpRequests to another domain. Such "cross-domain" requests would otherwise be forbidden by web browsers, per the same origin security policy. CORS defines a way in which the browser and the server can interact to determine whether or not to allow the cross-origin request. It is more powerful than only allowing same-origin requests, but it is more secure than simply allowing all such cross-origin requests.” ----Wikipedia[3]
通過在HTTP Header中加入擴充欄位,伺服器在相應網頁頭部加入欄位表示允許訪問的domain和HTTP method,用戶端檢查自己的域是否在允許清單中,決定是否處理響應。
實現的基礎是JavaScript不能夠操作HTTP Header。某些瀏覽器外掛程式實際上是具有這個能力的。

伺服器端在HTTP的回應標頭中加入(頁面層次的控制模式):
Access-Control-Allow-Origin: example.com
Access-Control-Request-Method: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization, Accept, Range, Origin

Access-Control-Expose-Headers: Content-Range
Access-Control-Max-Age: 3600
多個網域名稱之間用逗號分隔,表示對所示網域名稱提供跨域存取權限。"*"表示允許所有網域名稱的跨域訪問。

用戶端可以有兩種行為:
1. 發送OPTIONS請求,請求Access-Control資訊。如果自己的網域名稱在允許的訪問列表中,則發送真正的請求,否則放棄請求發送。
 2. 直接發送請求,然後檢查response的Access-Control資訊,如果自己的網域名稱在允許的訪問列表中,則讀取response body,否則放棄。
 本質上服務端的response內容已經到達本地,JavaScript決定是否要去讀取。

 Support: [Javascript Web Applications]
 * IE >= 8 (需要安裝caveat)
 * Firefox >= 3
 * Safari 完全支援
 * Chrome 完全支援
 * Opera 不支援

 2.3.2 測試

 測試頁面http://localhost:8080/test3.html使用jquery發送Ajax請求。

<html>  

        <head><title>testing cross sop</title></head>  

        <body>  

            Testing.  

            <script src="jquery-2.0.0.min.js"></script>  

            <script type='text/javascript'>  

                $.ajax({  

                    url: 'http://localhost:8000/hello',  

                    success: function(data) {  

                        alert(data);  

                    },  

                    error: function() {  

                        alert('error');  

                    }  

                });  

            </script>  

        </body>  

</html>  


 測試Restful API(http://localhost:8000/hello/{name})使用bottle.py來host。


from bottle import route, run, response

@route('/hello')
def index():
 return 'Hello World.'
 run(host='localhost', port=8000)


測試1:

 測試正常的跨域請求的行為。

測試結果:
 1. 跨域GET請求已經發出,請求header中帶有
  Origin    http://localhost:8080
 2. 伺服器端正確給出response
 3. Javascript拒絕讀取資料,在firebug中發現reponse為空白,並且觸發error回調。


測試2:
測試支援CORS的伺服器的跨域請求行為。
對Restful API做如下改動,在response中加入header:
def index():
 #Add CORS header#
 response.set_header("Access-Control-Allow-Origin", "http://localhost:8080")
return 'Hello World.'
測試結果:
1. 跨域GET請求已經發出,請求header中帶有
 Origin    http://localhost:8080
2. 伺服器端正確給出response
3. 用戶端正常擷取資料

測試3:
測試OPTIONS請求擷取CORS資訊。

對用戶端的Ajax請求增加header:        

$.ajax({  

          url: 'http://localhost:8000/hello',  

          headers: {'Content-Type': 'text/html'},  

          success: function(data) {  

              alert(data);  

          },  

          error: function() {  

              alert('error');  

          }  

      });  


  對Restful API做如下改動:
 

@route('/hello', method = ['OPTIONS', 'GET'])

            def index():

                if request.method == 'OPTIONS':

                    return ''

                return 'Hello World.'


 測試結果:
 1. Ajax函數會首先發送OPTIONS請求
 2. 針對OPTIONS請求伺服器
  3. 用戶端發現沒有CORS header後不會發送GET請求

 測試4:
 增加伺服器端對OPTIONS方法的處理。
  對Restful API做如下改動:
  

@route('/hello', method = ['OPTIONS', 'GET'])
            def index():
                response.headers['Access-Control-Allow-Origin'] = 'http://localhost:8080'
                response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
                response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type'
                if request.method == 'OPTIONS':
                    return ''
                return 'Hello World.'          


測試結果:
 1、Ajax函數會首先發送OPTIONS請求
 2、針對OPTIONS請求伺服器
 3、用戶端匹配CORS header中的allow headers and orgin後會正確發送GET請求並擷取結果
測試發現,Access-Control-Allow-Headers是必須的。

CORS協議提升了Ajax的跨域能力,但也增加了風險。一旦網站被注入指令碼或XSS攻擊,將非常方便的擷取使用者資訊並悄悄傳遞出去。


4、 Cookie 同源策略


Cookie中的同源只關注網域名稱,忽略協議和連接埠。所以https://localhost:8080/和http://localhost:8081/的Cookie是共用的。


5、Flash/SilverLight跨域


 瀏覽器的各種外掛程式也存在跨域需求。通常是通過在伺服器配置crossdomain.xml[4],設定本服務允許哪些網域名稱的跨域訪問。
用戶端會首先請求此檔案,如果發現自己的網域名稱在訪問列表裡,就發起真正的請求,否則不發送請求。


<?xml version="1.0"?>
            <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
            <cross-domain-policy>
            <allow-access-from domain="*"/>
            <allow-http-request-headers-from domain="*" headers="*"/>
        </cross-domain-policy>


通常crossdomain.xml放置在網站根目錄。 6、總結


互連網的發展催生了跨域訪問的需求,各種跨域方法和協議滿足了需求但也增加了各種風險。尤其是XSS和CSRF等攻擊的盛行也得益於此。
瞭解這些技術背景有助於在實際項目中熟練應用並規避各種安全風險。



廣告內容

關注我們

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.