【轉】同源策略和跨域請求解決方案

1、一個源的定義

若是兩個頁面的協議,端口(若是有指定)和域名都相同,則兩個頁面具備相同的源。
舉個例子:javascript

      下表給出了相對http://a.xyz.com/dir/page.html同源檢測的示例: 
      URL                                         結果          緣由
      http://a.xyz.com/dir2/other.html            成功     協議,端口(若是有指定)和域名都相同
      http://a.xyz.com/dir/inner/another.html     成功    協議,端口(若是有指定)和域名都相同 
      https://a.xyz.com/secure.html               失敗    不一樣協議 ( https和http )
      http://a.xyz.com:81/dir/etc.html            失敗    不一樣端口 ( 81和80)
      http://a.opq.com/dir/other.html             失敗    不一樣域名 ( xyz和opq)

 

2、同源策略是什麼?

同源策略是瀏覽器的一個安全功能,不一樣源的客戶端腳本在沒有明確受權的狀況下,不能讀寫對方資源。因此xyz.com下的js腳本採用ajax讀取abc.com裏面的文件數據是會被拒絕的。css

同源策略限制了從同一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。html

3、基於jsonp實現的跨域請求

  1. 頁面中的連接,重定向以及表單提交是不會受到同源策略限制的。java

  2. 跨域資源的引入是能夠的。可是js不能讀寫加載的內容。如嵌入到頁面中的<script src="..."></script>,<img>,<link>,<iframe>等。jquery

下面來分步舉例詳細闡述其中的奧妙:ajax

一、先開兩個項目,

項目1(http://127.0.0.1:8000/)
項目2(http://127.0.0.1:8100/)django

項目1json

url:
url(r'index1/$',views.index1)

views:

def index1(request):
return  HttpResponse('wangjifei')

 

項目2後端

url:
url(r'index2/$',views.index2)

views  :
def index2(request):
    return render(request,'index2.html')

index2.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>

<button id="btn">提交</button>

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script>
    $('#btn').click(function () {
        $.ajax({
            url:"http://127.0.0.1:8000/index1/",
            type:'get',
            success:function (res) {
                console.log(res)
           }
        })
    })
</script>
</body>
</html>

 

如今,打開使用瀏覽器打開 http://127.0.0.1:8100/index2/,點擊頁面上的 '提交' 按鈕,會在console頁面發現錯誤信息以下:跨域

 

 

爲何報錯呢?由於同源策略限制跨域發送ajax請求。
細心點的同窗應該會發現咱們的demo1項目其實已經接收到了請求並返回了響應,是瀏覽器對非同源請求返回的結果作了攔截。
再細心點的同窗會發現,咱們使用cdn方式引用的jQuery文件也是跨域的,它就可使用。
一樣是從其餘的站點拿東西,script標籤就能夠。那咱們能不能利用這一點搞點事情呢?

二、把index2.html中的代碼改一下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>

<button id="btn">提交</button>

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script src="http://127.0.0.1:8000/index1/"></script>
</body>
</html>

 

如今刷新一下會出現以下錯誤:

 

 
 

看來後端返回的響應已經被拿到了,只不過把wangjifei當成了一個變量來使用,可是該頁面上卻沒有定義一個名爲wangjifei的變量。因此出錯了。

三、那咱們就在index2.html中定義一個wangjifei變量看看:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>

<button id="btn">提交</button>

<script>
    var wangjifei = 123
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script src="http://127.0.0.1:8000/index1/"></script>
</body>
</html>

 

刷新發現不報錯了,

 

 

四、我定義一個變量能夠,那可不能夠定義一個函數呢?

index2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>

<button id="btn">提交</button>

<script>
    function wangjifei() {
        console.log('出手就要專業')
    }
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script src="http://127.0.0.1:8000/index1/"></script>
</body>
</html>

 

項目1中的views:也修改一下

def index1(request):
return  HttpResponse('wangjifei()')

 

刷新一下頁面顯示結果:

 

 

 

結果分析:返回的 wangjifei(),頁面上拿到這個響應以後直接執行了wangjifei函數!

五、那函數中可不能夠傳遞參數呢?咱們試一下!

index2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>

<button id="btn">提交</button>

<script>
    function wangjifei(res) {
         console.log(res)
    }
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script src="http://127.0.0.1:8000/index1/"></script>
</body>
</html>

 

項目1中的 views

from django.http import HttpResponse
import json

def index1(request):
    ret={'code':1,'msg':[110,119,120,12306]}
    res = json.dumps(ret)
    return  HttpResponse(f'wangjifei({res})')

 

刷新以後顯示結果:

 

 

果真傳遞參數也是能夠的!咱們經過script標籤的跨域特性來繞過同源策略拿到想要的數據了!!!

這其實就是JSONP的簡單實現模式,或者說是JSONP的原型:建立一個回調函數,而後在遠程服務上調用這個函數而且將JSON 數據形式做爲參數傳遞,完成回調。
將JSON數據填充進回調函數,這就是JSONP的JSON+Padding的含義。
可是咱們更多時候是但願經過事件觸發數據的獲取,而不是像上面同樣頁面一刷新就執行了,這樣很不靈活。

六、咱們能夠經過javascript動態的建立script標籤來實現。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>

<button id="btn">提交</button>

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script>
    //自定義的函數
    function wangjifei(res) {
        console.log(res)
    }
    //jquery給button綁定點擊事件
    $('#btn').click(function () {
        //建立一個script標籤
        var scriptEle = document.createElement('script');
        //給標籤添加src屬性,並添加對應的屬性值    http://127.0.0.1:8000/index1
        $(scriptEle).attr('src','http://127.0.0.1:8000/index1');
        //將建立好的標籤添加到頁面中,標籤添加後就會自動觸發get請求
        $('body').append(scriptEle);
        //將標籤移除
        $(scriptEle).remove()
    })
</script>
</body>
</html>

 

這樣當咱們點擊button按鈕的時候,會在頁面上插入一個script標籤,而後從後端獲取數據後再刪除掉。

七、爲了實現更加靈活的調用,咱們能夠把客戶端定義的回調函數的函數名傳給服務端,服務端則會返回以該回調函數名,將獲取的json數據傳入這個函數完成回調。這樣就能實現動態的調用了。修改代碼以下:

index2.html代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>

<button id="btn">提交</button>

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script>
    //自定義的函數
    function xxx(res) {
        console.log(res)
    }
    //jquery給button綁定點擊事件
    $('#btn').click(function () {
        //建立一個script標籤
        var scriptEle = document.createElement('script');
        //給標籤添加src屬性,並添加對應的屬性值    http://127.0.0.1:8000/index1?callback=xxx
        $(scriptEle).attr('src','http://127.0.0.1:8000/index1?callback=xxx');
        //將建立好的標籤添加到頁面中,標籤添加後就會自動觸發get請求
        $('body').append(scriptEle);
        //將標籤移除
        $(scriptEle).remove()
    })
</script>
</body>
</html>

 

項目1中views:

from django.http import HttpResponse
import json

def index1(request):
    ret={'code':1,'msg':[110,119,120,12306]}
    res = json.dumps(ret)
    callback = request.GET.get('callback')
    return  HttpResponse(f'{callback}({res})')

 

4、jQuery中getJSON方法介紹:

一、jQuery中有專門的方法實現jsonp。

index2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>
<button id="btn">提交</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">        </script>
<script>
    //jquery給button綁定點擊事件
    $('#btn').click(function () {
        $.getJSON("http://127.0.0.1:8000/index1?callback=?",function (res) {
            console.log(res)
        })
    })
</script>
</body>
</html>

 

要注意的是在url的後面必需要有一個callback參數,這樣getJSON方法纔會知道是用JSONP方式去訪問服務,callback後面的那個?是jQuery內部自動生成的一個回調函數名。

二、可是若是咱們想本身指定回調函數名,或者說服務上規定了回調函數名該怎麼辦呢?咱們可使用$.ajax方法來實現:

index2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>
<button id="btn">提交</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script>
    //jquery給button綁定點擊事件
    $('#btn').click(function () {
        $.ajax({
            //要訪問的url
            url:"http://127.0.0.1:8000/index1/",
            //要處理的數據類型jsonp
            dataType:'jsonp',
            //自定義回調函數名必要參數
            jsonp:'callback',
            //自定義回調函數名,url中callback=後面的函數名
            jsonpcallback:'wangjifei'
        })
    });
    //回調函數
    function wangjifei(res) {
        console.log(res)
    }
</script>
</body>
</html>

 

views:

from django.http import HttpResponse
import json

def index1(request):
    ret={'code':1,'msg':[110,119,120,12306]}
    res = json.dumps(ret)
    callback = request.GET.get('callback')
    return  HttpResponse(f'wangjifei({res})')

 

三、用ajax技術一般將回調函數寫在成功回調函數的位置:

index2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同源策略</title>
</head>
<body>
<button id="btn">提交</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">    </script>
<script>
    //jquery給button綁定點擊事件
    $('#btn').click(function () {
        $.ajax({
            //要訪問的url
            url:"http://127.0.0.1:8000/index1/",
            //要處理的數據類型jsonp
            dataType:'jsonp',
            //success回調
            success:function (res) {
                console.log(res)
            }
        })
    });
    //回調函數
    function wangjifei(res) {
        console.log(res)
    }
</script>
</body>
</html>

 

最後來一個jsonp的實際應用:

 <!DOCTYPE html>
     <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>同源策略</title>
    </head>
    <body>
    <button id="show-tv">提交</button>
    <div class="tv-list"></div>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
        $("#show-tv").click(function () {
            $.ajax({
                url: "http://www.jxntv.cn/data/jmd-jxtv2.html?  callback=list&_=1454376870403",
                dataType: 'jsonp',
                jsonp: 'callback',
                jsonpCallback: 'list',
                success: function (data) {
                    var weekList = data.data;
                    console.log(weekList);
                    var $tvListEle = $(".tv-list");
                    $.each(weekList, function (k, v) {
                        var s1 = "<p>" + v.week + "列表</p>";
                        $tvListEle.append(s1);
                        $.each(v.list, function (k2, v2) {
                            var s2 = "<p><a href='" + v2.link + "'>" + v2.name + "</a></p>";
                            $tvListEle.append(s2)
                        });
                        $tvListEle.append("<hr>");
                    })
                }
            })
        });
    </script>
    </body>
    </html>

 

5、基於Core方法解決跨域請求

  • 咱們介紹了jsonp解決跨域請求問題,這種解決方式很好的詮釋了跨域請求的本質,可是略顯麻煩,是否還記得在咱們不作任何處理的時候,跨域請求時候瀏覽器給咱們報的錯誤不?翻譯過來就是由於響應頭沒有指定Access-Control-Allow-Origin所容許原始的請求路徑,所以原始請求路徑http://127.0.0.1:8001不被容許訪問。 基於上述的緣由解釋,咱們只須要在響應的內容加入上述這樣的受權字段,即可解決。

  • 簡單請求的定義:
    只要同時知足如下兩大條件,就屬於簡單請求,不知足就是複雜請求!!!
    1.(1) 請求方法是如下三種方法之一:
    -- HEAD,GET,POST
    2.(2)HTTP的頭信息不超出如下幾種字段:
    -- Accept
    -- Accept-Language
    -- Content-Language
    -- Last-Event-ID
    -- Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

因爲django的全部請求響應都要走中間件,因此能夠寫一個跨域的中間件來解決跨域問題

from django.utils.deprecation import MiddlewareMixin
class MyCore(MiddlewareMixin):
    def process_response(self, request, response):
        response['Access-Control-Allow-Origin'] = "*"  //簡單請求
        if request.method == "OPTIONS":
            # 複雜請求 預檢
            response['Access-Control-Allow-Headers'] = "Content-Type"
            response['Access-Control-Allow-Methods'] = "POST, DELETE, PUT"
        return response

 

 
相關文章
相關標籤/搜索