第二百七十四節,同源策略和跨域Ajax

同源策略和跨域Ajaxjavascript

 

什麼是同源策略css

  儘管瀏覽器的安全措施多種多樣,可是要想黑掉一個Web應用,只要在瀏覽器的多種安全措施中找到某種措施的一個漏洞或者繞過一種安全措施的方法便可。瀏覽器的各類保安措施之間都試圖保持相互獨立,可是攻擊者只要能在出錯的地方注入少量JavaScript,全部安全控制幾乎所有瓦解——最後還起做用的就是最弱的安全防線:同源策略。同源策略管轄着全部保安措施,然而,因爲瀏覽器及其插件,諸如Acrobat Reader、Flash 和Outlook Express漏洞頻出,導致同源策略也頻頻告破。
  既然web應用的最弱安全防線是同源策略,那什麼是同源策略呢?如何去攻破同源策略呢?如何黑web應用呢?
  同源策略,它是由Netscape提出的一個著名的安全策略。全部支持javascript的網站都會使用同源策略來保護本身的web應用。
  同源策略又名同域策略,通俗易懂的來講,同源就是(主機名+協議+端口號【若存在】)三者相同。也就是說javascript只能夠操做本身域下的東西,不能操做其餘域下的東西。好比百度下javascript是不可操做谷歌下的頁面。

  那爲何要同源策略呢?
  如上述所述,同源策略,javascript操做本身web應用,不得操做別人web應用。既然如此,也就是別人沒法操做本身web應用,是保證web安全的一種方式。
  假設別人能夠操做本身web應用,試想是否很可怕?假如你打開網銀頁面,蹦出一個惡意廣告,當你關閉廣告,若沒有同源策略,是否是表明惡意廣告會操做你網銀頁面,經過javascript竊取你網銀的信息。
  那瞭解了同源策略,下面舉幾個url判斷是否在同一個域中。
  假設url地址:http://store.company.com/dir/page.html檢測下面是否他的同源地址。
  其url的協議:http;主機名:store.company.com 端口號沒有或者其相應服務器的默認值


URL 結果 緣由
http://store.company.com/dir2/other.html
http://store.company.com/dir/inner/another.html
https://store.company.com/secure.html 不是 協議不相同
http://store.company.com:81/dir/etc.html 不是 端口號不相同
http://news.company.com/dir/other.html 不是 主機名不相同

 
 
違背了同源策略,有些跨域請求會被瀏覽器攔截的
 
在瀏覽器中,<script>、<img>、<iframe>、<link>等標籤均可以加載跨域資源,而不受同源限制,但瀏覽器限制了JavaScript的權限使其不能讀、寫加載的內容。也就是說不一樣源的JavaScript是沒法獲取HTML元素,和JavaScript通信請求的
  
另外同源策略只對網頁的HTML文檔作了限制,對加載的其餘靜態資源如javascript、css、圖片等仍然認爲屬於同源。
 
 
下面講解ajax跨域請求被阻止
 
舉例:
首先在本地hosts文件配置兩個域名訪問IP做爲測試用

127.0.0.1 www.fwd.com
127.0.0.1 www.khd.com html

 
http://www.khd.com:8002/khd 文件裏用ajax請求http://www.fwd.com:8001/fwd
 
http://www.khd.com:8002/khd
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script>
</head>
<body>
<input type="button" value="提交" onclick="doajax();"/>
<script>
    function doajax() { $.ajax({ url: 'http://www.fwd.com:8001/fwd', type: 'POST', data:{'k1':'v1'}, success: function (responseText, statusText) { }, error: function (event, errorText, errorType) { } }); } </script>
</body>
</html>

http://www.fwd.com:8001/fwdjava

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script>
    <script type="text/javascript" src='{{static_url("jquery.form.js")}}' charset="UTF-8"></script>
</head>
<body>
服務端
<script>

</script>
</body>
</html>

此時點擊按鈕發起ajax請求後,能夠看到請求已被瀏覽器的同源策略阻止python

 
跨域阻止原理圖
 
 
 
 突破瀏覽器同源策略ajax跨域請求,有兩個辦法
 
辦法1、JSONP方式
 
自定義JSONP跨域請求數據
 
只能是get方法的請求,也就是利用script標籤和script標籤的src發送get請求
1在要請求數據的頁面用js建立一個script標籤src等於要請求數據的地址
2響應頁面返回的必須是一個帶有參數的函數名稱
3自定義對應響應頁面返回的函數,這樣自定義的函數也就會執行了,就能獲得數據
請求頁面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script>
</head>
<body>
<input type="button" value="跨域提交" onclick="doajax();"/>
<script>
    //利用原生js本身建立一個跨域請求
    function doajax() { //當點擊提交按鈕是執行函數
        var tag = document.createElement('script'); //建立一個script標籤
 tag.src = 'http://www.fwd.com:8001/fwd'; //設置script標籤的src地址,爲要請求數據的地址,由於src是不受同源策略
 document.head.appendChild(tag); //將script標籤添加到head標籤裏面
 document.head.removeChild(tag); //添加後會當即發送請求,因此這裏能夠將添加的script標籤刪除了
    }
    //響應網站返回,shuju({'k1':'v1'}); 也就至關於返回了一個帶有參數的函數名稱
    function shuju(shj) { //自定義執行函數,和響應網站返回的名稱相同,也就會執行自定義函數,而參數就是響應網站返回的數據
        for(var i in shj){ //循環響應網站返回的數據
 alert(i + ':' + shj[i]); //打印出數據的鍵和值
 } } </script>
</body>
</html>

響應端jquery

import tornado.ioloop
import tornado.web                                              #導入tornado模塊下的web文件


class fwdHandler(tornado.web.RequestHandler):
    def get(self):                                      #接收get請求
        # self.render("fwd.html")
        self.write("shuju({'k1':'v1'});")               #返回數據  
    def post(self):
        self.write("post請求成功")

settings = {                                            #html文件歸類配置,設置一個字典
    "template_path":"views",                            #鍵爲template_path固定的,值爲要存放HTML的文件夾名稱
    "static_path":"statics",                            #鍵爲static_path固定的,值爲要存放js和css的文件夾名稱
}

#路由映射
application = tornado.web.Application([                 #建立一個變量等於tornado.web下的Application方法
    (r"/fwd", fwdHandler),
],**settings)                                           #將html文件歸類配置字典,寫在路由映射的第二個參數裏

if __name__ == "__main__":
    #內部socket運行起來
    application.listen(8001)                            #設置端口
    tornado.ioloop.IOLoop.instance().start()

 
 
ajax的JSONP方式
ajax跨域請求本質上就是上面咱們自定義跨域的方式,只不過ajax幫咱們封裝好了,不用咱們去建立script標籤
請求頁面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script>
</head>
<body>
<input type="button" value="跨域提交" onclick="doajax();"/>
<script>
    function doajax() { //當點擊提交按鈕是執行函數
 $.ajax({ type: 'GET', url: 'http://www.fwd.com:8001/fwd', dataType: 'jsonp', //JSONP,數據類型
 jsonpCallBack:'shj'  //接收響應頁面帶有參數的函數名稱
 }); } //響應網站返回,shuju({'k1':'v1'}); 也就至關於返回了一個帶有參數的函數名稱
    function shuju(shj) { //自定義執行函數,和響應網站返回的名稱相同,也就會執行自定義函數,而參數就是響應網站返回的數據
        for (var i in shj) { //循環響應網站返回的數據
 alert(i + ':' + shj[i]); //打印出數據的鍵和值
 } } </script>
</body>

響應端web

#!/usr/bin/env python
#coding:utf-8

import tornado.ioloop
import tornado.web                                              #導入tornado模塊下的web文件


class fwdHandler(tornado.web.RequestHandler):
    def get(self):                                      #接收get請求
        # self.render("fwd.html")
        self.write("shuju({'k1':'v1'});")               #返回數據
    def post(self):
        self.write("post請求成功")

settings = {                                            #html文件歸類配置,設置一個字典
    "template_path":"views",                            #鍵爲template_path固定的,值爲要存放HTML的文件夾名稱
    "static_path":"statics",                            #鍵爲static_path固定的,值爲要存放js和css的文件夾名稱
}

#路由映射
application = tornado.web.Application([                 #建立一個變量等於tornado.web下的Application方法
    (r"/fwd", fwdHandler),
],**settings)                                           #將html文件歸類配置字典,寫在路由映射的第二個參數裏

if __name__ == "__main__":
    #內部socket運行起來
    application.listen(8001)                            #設置端口
    tornado.ioloop.IOLoop.instance().start()

 
 
 
方法2、基於CORS實現跨域Ajax
請求頁面不變就像同域同樣,只是響應端返回數據的時候帶有響應頭標識,也就是告訴瀏覽器容許跨域請求,這是最新的方式,新瀏覽器支持,老瀏覽器不支持,未來會都支持
隨着技術的發展,如今的瀏覽器能夠支持主動設置從而容許跨域請求,即:跨域資源共享(CORS,Cross-Origin Resource Sharing),其本質是設置響應頭,使得瀏覽器容許跨域請求。
 
CORS實現跨域Ajax有兩種方式,簡單方式和複雜方式兩種

簡單請求和非簡單請求的區別?
簡單請求:一次請求
非簡單請求:兩次請求,在發送數據以前會先發一次請求用於作「預檢」,只有「預檢」經過後纔再發送一次請求用於數據傳輸。ajax

簡單請求
簡單請求:一次請求

條件:
  一、請求方式:HEAD、GET、POST
  二、請求頭信息:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type 對應的值是如下三個中的任意一個
      application/x-www-form-urlencoded
      multipart/form-data
      text/plain
注意:同時知足以上兩個條件時,則是簡單請求,不然爲複雜請求json

 

self.set_header()方法,在響應端的邏輯處理的get或者post裏使用,功能:給返回數據加上響應頭標識告訴瀏覽器容許跨域請求
使用方法:兩個參數,參數1,響應頭標識,參數2,容許跨域請求的域名(多個域名,號隔開),(*表明全部域名支持跨域請求)
self.set_header('Access-Control-Allow-Origin','http://www.jxiou.com/')
self.set_header('Access-Control-Allow-Origin','*')跨域

響應端

#!/usr/bin/env python
#coding:utf-8

import tornado.ioloop
import tornado.web                                              #導入tornado模塊下的web文件


class fwdHandler(tornado.web.RequestHandler):
    def get(self):                                      #接收get請求
        pass
    def post(self):  self.set_header('Access-Control-Allow-Origin','*') self.write("{'k1':'v1'}")  # 返回數據

settings = {                                            #html文件歸類配置,設置一個字典
    "template_path":"views",                            #鍵爲template_path固定的,值爲要存放HTML的文件夾名稱
    "static_path":"statics",                            #鍵爲static_path固定的,值爲要存放js和css的文件夾名稱
}

#路由映射
application = tornado.web.Application([                 #建立一個變量等於tornado.web下的Application方法
    (r"/fwd", fwdHandler),
],**settings)                                           #將html文件歸類配置字典,寫在路由映射的第二個參數裏

if __name__ == "__main__":
    #內部socket運行起來
    application.listen(8001)                            #設置端口
    tornado.ioloop.IOLoop.instance().start()

請求頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script>
</head>
<body>
<input type="button" value="跨域提交" onclick="doajax();"/>
<script>
    function doajax() { //當點擊提交按鈕是執行函數
 $.ajax({ type: 'POST', url: 'http://www.fwd.com:8001/fwd', data: {"a":'b'}, success: function (response, status, xhr) { alert(response); //打印返回數據
 } }); } </script>
</body>
</html>

 

 

 

 

複雜請求

複雜請求:兩次請求,在發送數據以前會先發一次OPTIONS請求用於作「預檢」,只有「預檢」經過後纔再發送一次請求用於數據傳輸。

self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 預檢請求或者數據請求時容許跨域,容許跨域的域名(多個逗號隔開)(*標識全部域名容許)
self.set_header('Access-Control-Allow-Headers', "k1,k2") 預檢請求時容許請求頁面ajax設置請求頭headers屬性跨域,容許跨域的headers屬性請求頭(多個逗號隔開)
self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") 預檢請求時容許請求頁面的請求方式跨域,容許跨域的請求方式(多個逗號隔開)
self.set_header('Access-Control-Max-Age', 10) 預檢請求時設置預檢有效時間,如設置10,表示此次預檢後10秒後的請求再次預檢

響應端

#!/usr/bin/env python
#coding:utf-8

import tornado.ioloop
import tornado.web                                              #導入tornado模塊下的web文件


class fwdHandler(tornado.web.RequestHandler):
    def get(self):                                      #接收get請求
        pass
    def options(self): #接收預檢的options請求 self.set_header('Access-Control-Allow-Origin', '*') #預檢請求或者數據請求時容許跨域,容許跨域的域名(多個逗號隔開)(*標識全部域名容許) self.set_header('Access-Control-Allow-Methods', 'PUT') #預檢請求時容許請求頁面的請求方式跨域,容許跨域的請求方式(多個逗號隔開) self.set_header('Access-Control-Allow-Headers', "k1,k2") #預檢請求時容許請求頁面ajax設置請求頭headers屬性跨域,容許跨域的headers屬性請求頭(多個逗號隔開) def put(self): self.set_header('Access-Control-Allow-Origin','*') self.write("{'k1':'v1'}") # 返回數據

settings = {                                            #html文件歸類配置,設置一個字典
    "template_path":"views",                            #鍵爲template_path固定的,值爲要存放HTML的文件夾名稱
    "static_path":"statics",                            #鍵爲static_path固定的,值爲要存放js和css的文件夾名稱
}

#路由映射
application = tornado.web.Application([                 #建立一個變量等於tornado.web下的Application方法
    (r"/fwd", fwdHandler),
],**settings)                                           #將html文件歸類配置字典,寫在路由映射的第二個參數裏

if __name__ == "__main__":
    #內部socket運行起來
    application.listen(8001)                            #設置端口
    tornado.ioloop.IOLoop.instance().start()

請求頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script>
</head>
<body>
<input type="button" value="跨域提交" onclick="doajax();"/>
<script>
    function doajax() { //當點擊提交按鈕是執行函數
 $.ajax({ type: 'PUT', //複雜請求
 url: 'http://www.fwd.com:8001/fwd', headers: {'k1': 'qqtou'}, //設置請求頭
 data: {"a":'b'}, success: function (response, status, xhr) { alert(response); //打印返回數據
 } }); } </script>
</body>
</html>

 

 

 

跨域傳輸cookie

在跨域請求中,默認狀況下,HTTP Authentication信息,Cookie頭以及用戶的SSL證書不管在預檢請求中或是在實際請求都是不會被髮送。

若是想要發送:
  瀏覽器端ajax屬性:xhrFields的withCredentials爲true
  服務器端的預檢請求和數據請求:Access-Control-Allow-Credentials爲true
  注意:服務器端響應的 Access-Control-Allow-Origin 不能是通配符 *

響應端

/usr/bin/env python
#coding:utf-8

import tornado.ioloop
import tornado.web                                              #導入tornado模塊下的web文件


class fwdHandler(tornado.web.RequestHandler):
    def get(self):                                      #接收get請求
        pass
    def options(self):                                                                        #接收預檢的options請求
        self.set_header('Access-Control-Allow-Credentials', "true")                           #傳遞Cookie頭以及用戶的SSL證書
        self.set_header('Access-Control-Allow-Origin', 'http://www.khd.com:8002')             #預檢請求或者數據請求時容許跨域,容許跨域的域名
        self.set_header('Access-Control-Allow-Methods', 'PUT')                                #預檢請求時容許請求頁面的請求方式跨域,容許跨域的請求方式(多個逗號隔開)
        self.set_header('Access-Control-Allow-Headers', "k1,k2")                              #預檢請求時容許請求頁面ajax設置請求頭headers屬性跨域,容許跨域的headers屬性請求頭(多個逗號隔開)
    def put(self):
        self.set_header('Access-Control-Allow-Credentials', "true")                           # 傳遞Cookie頭以及用戶的SSL證書
        self.set_header('Access-Control-Allow-Origin','http://www.khd.com:8002')
        self.write("{'k1':'v1'}")                       # 返回數據
        self.set_cookie('kkkkk', 'vvvvv')               #寫入cookie
        print(self.get_cookie('kkkkk'))                 #獲取cookie

settings = {                                            #html文件歸類配置,設置一個字典
    "template_path":"views",                            #鍵爲template_path固定的,值爲要存放HTML的文件夾名稱
    "static_path":"statics",                            #鍵爲static_path固定的,值爲要存放js和css的文件夾名稱
}

#路由映射
application = tornado.web.Application([                 #建立一個變量等於tornado.web下的Application方法
    (r"/fwd", fwdHandler),
],**settings)                                           #將html文件歸類配置字典,寫在路由映射的第二個參數裏

if __name__ == "__main__":
    #內部socket運行起來
    application.listen(8001)                            #設置端口
    tornado.ioloop.IOLoop.instance().start()

請求頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script>
</head>
<body>
<input type="button" value="跨域提交" onclick="doajax();"/>
<script>
    function doajax() {                                 //當點擊提交按鈕是執行函數
        $.ajax({
            type: 'PUT',                                //複雜請求
            url: 'http://www.fwd.com:8001/fwd',
            headers: {'k1': 'qqtou'},                   //設置請求頭
 xhrFields:{withCredentials: true}, //傳遞Cookie頭以及用戶的SSL證書
            data: {"a":'b'},
            success: function (response, status, xhr) {
                alert(response);                            //打印返回數據
            }
        });
    }
</script>
</body>
</html>

 

 
 
 
 
 
補充,響應端設置響應頭,請求端是沒法獲取到響應端設置的響應頭的,若是要獲取到,響應端必須作設置容許請求端獲取

self.set_header('xxoo', "bili")  設置響應頭第一個是響應頭名稱,第二個是響應頭值
self.set_header('Access-Control-Expose-Headers', "xxoo,bili")    容許設置的響應頭,請求端獲取

 響應端
#!/usr/bin/env python
#coding:utf-8

import tornado.ioloop
import tornado.web                                              #導入tornado模塊下的web文件


class fwdHandler(tornado.web.RequestHandler):
    def get(self):                                      #接收get請求
        pass
    def options(self):                                                                        #接收預檢的options請求
        self.set_header('Access-Control-Allow-Credentials', "true")                           #傳遞Cookie頭以及用戶的SSL證書
        self.set_header('Access-Control-Allow-Origin', 'http://www.khd.com:8002')             #預檢請求或者數據請求時容許跨域,容許跨域的域名
        self.set_header('Access-Control-Allow-Methods', 'PUT')                                #預檢請求時容許請求頁面的請求方式跨域,容許跨域的請求方式(多個逗號隔開)
        self.set_header('Access-Control-Allow-Headers', "k1,k2")                              #預檢請求時容許請求頁面ajax設置請求頭headers屬性跨域,容許跨域的headers屬性請求頭(多個逗號隔開)
    def put(self):
        self.set_header('Access-Control-Allow-Credentials', "true")                           # 傳遞Cookie頭以及用戶的SSL證書
        self.set_header('Access-Control-Allow-Origin','http://www.khd.com:8002')
        self.set_header('xxoo', "bili")                                                      #設置響應頭第一個是響應頭名稱,第二個是響應頭值
        self.set_header('Access-Control-Expose-Headers', "xxoo,bili")                         #容許設置的響應頭,請求端獲取


        self.write("{'k1':'v1'}")                       # 返回數據
        self.set_cookie('kkkkk', 'vvvvv')               #寫入cookie
        print(self.get_cookie('kkkkk'))                 #獲取cookie

settings = {                                            #html文件歸類配置,設置一個字典
    "template_path":"views",                            #鍵爲template_path固定的,值爲要存放HTML的文件夾名稱
    "static_path":"statics",                            #鍵爲static_path固定的,值爲要存放js和css的文件夾名稱
}

#路由映射
application = tornado.web.Application([                 #建立一個變量等於tornado.web下的Application方法
    (r"/fwd", fwdHandler),
],**settings)                                           #將html文件歸類配置字典,寫在路由映射的第二個參數裏

if __name__ == "__main__":
    #內部socket運行起來
    application.listen(8001)                            #設置端口
    tornado.ioloop.IOLoop.instance().start()

相關文章
相關標籤/搜索