解决跨域问题-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
相关文章
相关标签/搜索