解決跨域問題-jsonp&cors

跨域的緣由

瀏覽器的同源策略

同源策略是瀏覽器上爲安全性考慮實施的很是重要的安全策略。javascript

指的是從一個域上加載的腳本不容許訪問另一個域的文檔屬性。 舉個例子:好比一個惡意網站的頁面經過iframe嵌入了銀行的登陸頁面(兩者不一樣源), 若是沒有同源限制,惡意網頁上的javascript腳本就能夠在用戶登陸銀行的時候獲取用戶名和密碼。html

何謂同源

URL由協議、域名、端口和路徑組成,若是兩個URL的協議、域名和端口相同,則表示它們同源。java

在瀏覽器中,<script>、<img>、<iframe>、<link>等標籤均可以加載跨域資源,而不受同源限制,但瀏覽器會限制腳本中發起的跨域請求。好比,使用 XMLHttpRequest 對象和Fetch發起 HTTP 請求就必須遵照同源策略。Web 應用程序經過 XMLHttpRequest 對象或Fetch能且只能向同域名的資源發起 HTTP 請求,而不能向任何其它域名發起請求。不容許跨域訪問並不是是瀏覽器限制了發起跨站請求,而是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了
python

解決

jsonp(JSON with Padding)

  • 原理

    首先在http:\\127.0.0.1:8000\test\下有以下返回字符串‘ok’的視圖:jquery

    from django.http import HttpResponse
    
    def test(request):
        return HttpResponse('ok')
    服務端

    而後在http:\\127.0.0.1:8001\域下直接用ajax發起一個跨域請求:ajax

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Test</title>
        <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    </head>
    <body>
    <script>
        $.get('http://127.0.0.1:8000/test/', function (data) {
            alert(data)
        })
    </script>
    </body>
    </html>
    瀏覽器端

    看效果(這裏我使用的是火狐瀏覽器,提示更直觀):django

    上面有提到過<script>等標籤能夠加載跨域資源,咱們試一下直接讓script標籤的src屬性指向http:\\127.0.0.1:8000\test\:json

    瀏覽器端

    會發現script發出的請求成功拿到響應結果:跨域

    可是控制檯有一個報錯:瀏覽器

    這個問題顯然是返回的內容(也就是‘ok’)被瀏覽器直接當作腳本執行,但window中並無定義名字對應爲‘ok’的變量。

    也就是說此方式請求在服務端返回的內容是能夠直接調用瀏覽器端js腳本的,此時咱們想,若是服務端返回一個方法調用,並這個方法在對應瀏覽器端js腳本中有存在,經過傳參的方式,是否是能夠間接拿到想要返回的內容。修改服務端和瀏覽器端:

    from django.http import HttpResponse
    
    def test(request):
        return HttpResponse('func("ok")')
    服務端
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Test</title>
        <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    </head>
    <body>
    <script>
        function func(data){
            console.info(data)
        }
    </script>
    <script src="http:\\127.0.0.1:8000\test\"></script>
    </body>
    </html>
    瀏覽器端

    此時會發現,瀏覽器端函數被成功調用,而且拿到了服務端返回的內容:

    在這裏上面的script標籤是硬編碼,顯然也能夠經過dom操做動態建立標籤間接發起請求,下面要介紹的jquery的ajax就幫咱們簡化了這些操做。

  • jquery提供的jsonp

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Test</title>
        <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    </head>
    <body>
    <script>
        $.ajax({
            url: "http://127.0.0.1:8000/test/",
            dataType: "jsonp",  // 指定服務器返回的數據類型。
            // jsonp: "funcKey",   // 指定參數名稱。
                       // 若是指定,請求url會帶有一組參數:funcKey=func。
                       // 不指定默認爲callback=func,具體視服務端狀況而定。
    jsonpCallback: "func", // 指定回調函數名稱。 success: function (data) { console.info(data); } }); </script> </body> </html>

    由於jquery提供的jsonp的實現方式其實就是<script>腳本請求地址的方式同樣,只是ajax的jsonp對其作了封裝,可想而知,jsonp是不支持POST方式的。

cors(Cross-origin resource sharing)

  • 使用

    咱們從新看下以前跨域失敗的錯誤:

    其實瀏覽器已經很明顯的告訴了咱們緣由:服務端響應缺乏一個‘Access-Control-Allow-Origin’頭,這個頭的做用咱們能夠理解爲服務端告訴瀏覽器端:「你能夠跨域訪問我」。修改服務端在服務端響應加上該響應頭:

    from django.http import HttpResponse
    
    def test(request):
        response = HttpResponse('ok')
        response["Access-Control-Allow-Origin"] = "*"
        return response
    服務端

    修改客戶端發送普通ajax請求:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Test</title>
        <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    </head>
    <body>
    <script>
        $.get('http://127.0.0.1:8000/test/',function(data){
            console.info(data)
        })
    </script>
    </body>
    </html>
    客戶端

    此時咱們會發現,加上該響應頭後客戶端就能夠像訪問本源地址訪問跨域地址:

  • 響應頭說明

    • Access-Control-Allow-Origin(必須)
      Access-Control-Allow-Origin=* //容許任何域名訪問
      Access-Control-Allow-Origin=http://127.0.0.1:8000 //僅容許指定域名訪問

      該請求頭必須包含在全部合法的CORS響應頭中;不然,省略該響應頭會致使CORS請求失敗。該值要麼與請求頭Origin的值同樣(如上述例子),要麼設置成星號‘*’,以匹配任意Origin。若是你想任何站點都能獲取到你的數據,那麼就使用‘*’吧。可是,若是你想有效的控制,就將該值設置爲一個實際的值。

    • Access-Control-Allow-Credentials(可選)
      Access-Control-Allow-Credentials=true

      默認狀況下,發送CORS請求,cookies是不會附帶發送的。可是,經過使用該響應頭就可讓cookies包含在CORS請求中。注意,該響應頭只有惟一的合法值true(所有小寫)。若是你不須要cookies值,就不要包含該響應頭了,而不是將該響應頭的值設置成false。該響應頭Access-Control-Allow-Credentials須要與XMLHttpRequest2對象的withCredentials屬性配合使用。當這兩個屬性同時設置爲true時,cookies才能附帶。例如,withCredentials被設置成true,可是響應頭中不包含 Access-Control-Allow-Credentials響應頭,那麼該請求就會失敗(反之亦然)。發送CORS請求時,最好不要攜帶cookies,除非你肯定你想在請求中包含cookie。

    • Access-Control-Expose-Headers(可選)
      Access-Control-Expose-Headers

      XMLHttpRequest2對象有一個getResponseHeader()方法,該方法返回一個特殊響應頭值。在一個CORS請求中,getResponseHeader()方法僅能獲取到簡單的響應頭,以下:

      Cache-Control
      Content-Language
      Content-Type
      Expires
      Last-Modified
      Pragma

      若是你想客戶端可以獲取到其餘的頭部信息,你必須設置Access-Control-Expose-Headers響應頭。該響應頭的值能夠爲響應頭的名稱,多個時須要利用逗號隔開,這樣客戶端就能經過getResponseHeader方法獲取到了。

  • 簡單請求&複雜請求

    • 條件
      1、請求方式:HEAD、GET、POST
      2、請求頭信息:
              Accept
              Accept-Language
              Content-Language
              Last-Event-ID
              Content-Type 對應的值是如下三個中的任意一個
                                      application/x-www-form-urlencoded
                                      multipart/form-data
                                      text/plain

      注意:同時知足以上兩個條件時,則是簡單請求,不然爲複雜請求。

    • 區別
      簡單請求:一次請求。
      複雜請求:兩次請求,在發送數據以前會先發一次請求用於作「預檢」,只有「預檢」經過後纔再發送一次請求用於數據傳輸。 
    • 關於預檢
      - 請求方式:OPTIONS
      - 「預檢」其實作檢查,檢查若是經過則容許傳輸數據,檢查不經過則再也不發送真正想要發送的消息
      - 如何「預檢」
           => 若是請求是PUT等複雜請求,則服務端須要設置容許某請求,不然「預檢」不經過
              Access-Control-Request-Method
           => 若是複雜請求設置了請求頭,則服務端須要設置容許某請求頭,不然「預檢」不經過
              Access-Control-Request-Headers
    • 複雜請求示例

      a、支持跨域,複雜請求。

      1、「預檢」請求時,在服務端設置容許的請求方式:Access-Control-Request-Method
      2、「預檢」請求時,在服務端設置容許的響應頭:Access-Control-Request-Headers
      3、「預檢」緩存時間,服務器設置響應頭:Access-Control-Max-Age
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>CorsTest</title>
          <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
      </head>
      <body>
      <script>
          (function JqSendRequest(){
                  $.ajax({
                      url: "http://127.0.0.1:8001/test/",
                      type: 'PUT',
                      dataType: 'text',
                      headers: {'k1': 'v1'},
                      success: function(data, statusText, xmlHttpRequest){
                          console.log(data);
                      }
                  })
              })()
      </script>
      </body>
      </html>
      HTML
      from django.shortcuts import render, HttpResponse
      
      from django.views import View
      
      
      class TestView(View):
          def options(self, request, *args, **kwargs):
              response = HttpResponse()
              response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
              response['Access-Control-Allow-Headers'] = "k1,k2"
              response['Access-Control-Allow-Methods'] = "PUT,DELETE"
              response['Access-Control-Max-Age'] = 1
              return response
      
          def put(self, request, *args, **kwargs):
              response = HttpResponse()
              response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
              return HttpResponse('ok')
      Django

      b、跨域獲取自定義響應頭。

      默認獲取到的全部響應頭只有基本信息,若是想要獲取自定義的響應頭,則須要再服務器端設置Access-Control-Expose-Headers。
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>CorsTest</title>
          <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
      </head>
      <body>
      <script>
          (
              function JqSendRequest(){
                  $.ajax({
                      url: "http://127.0.0.1:8001/test/",
                      type: 'PUT',
                      dataType: 'text',
                      headers: {'k1': 'v1'},
                      success: function(data, statusText, xmlHttpRequest){
                          console.log(data);
                          // 獲取響應頭
                          console.log(xmlHttpRequest.getAllResponseHeaders());
                      }
                  })
              })()
      </script>
      </body>
      </html>
      HTML
      from django.shortcuts import render, HttpResponse
      
      from django.views import View
      
      
      class TestView(View):
          def options(self, request, *args, **kwargs):
              response = HttpResponse()
              response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
              response['Access-Control-Allow-Headers'] = "k1,k2"
              response['Access-Control-Allow-Methods'] = "PUT,DELETE"
              response['Access-Control-Max-Age'] = 1
              return response
      
          def put(self, request, *args, **kwargs):
              response = HttpResponse('ok')
              response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
              response['testkey1'] = "testval1"
              response['testkey2'] = "testval2"
              response['Access-Control-Expose-Headers'] = "testkey1,testkey2"
              return response
      Django

      c、跨域傳輸cookie。

      在跨域請求中,默認狀況下,HTTP Authentication信息,Cookie頭以及用戶的SSL證書不管在預檢請求中或是在實際請求都是不會被髮送。
      
      若是想要發送:
          瀏覽器端:XMLHttpRequest的withCredentials爲true
          服務器端:Access-Control-Allow-Credentials爲true
          注意:服務器端響應的 Access-Control-Allow-Origin 不能是通配符 *
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>CorsTest</title>
          <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
      </head>
      <body>
      <script>
          (
              function JqSendRequest() {
                  $.ajax({
                      url: "http://127.0.0.1:8001/test/",
                      type: 'PUT',
                      dataType: 'text',
                      headers: {'k1': 'v1'},
                      xhrFields: {withCredentials: true},
                      success: function (data, statusText, xmlHttpRequest) {
                          console.log(data);
                      }
                  })
              })()
      </script>
      </body>
      </html>
      HTML
      from django.shortcuts import render, HttpResponse
      
      from django.views import View
      
      
      class TestView(View):
          def options(self, request, *args, **kwargs):
              response = HttpResponse()
              response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
              response['Access-Control-Allow-Headers'] = "k1,k2"
              response['Access-Control-Allow-Methods'] = "PUT,DELETE"
              response['Access-Control-Max-Age'] = 1
              response['Access-Control-Allow-Credentials'] = 'true'
              return response
      
          def put(self, request, *args, **kwargs):
              response = HttpResponse('ok')
              response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
              response['Access-Control-Allow-Credentials'] = 'true'
              response['testkey1'] = "testval1"
              response['testkey2'] = "testval2"
              response['Access-Control-Expose-Headers'] = "testkey1,testkey2"
              response.set_cookie('my_cookie', 'cookie value')
              return response
      Django
相關文章
相關標籤/搜索