跨域的原因

浏览器的同源策略

同源策略是浏览器上为安全性考虑实施的非常重要的安全策略。

指的是从一个域上加载的脚本不允许访问另外一个域的文档属性。 举个例子:比如一个恶意网站的页面通过iframe嵌入了银行的登录页面(二者不同源), 如果没有同源限制,恶意网页上的javascript脚本就可以在用户登录银行的时候获取用户名和密码。

何谓同源

URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示它们同源。

在浏览器中,<script>、<img>、<iframe>、<link>等标签都可以加载跨域资源,而不受同源限制,但浏览器会限制脚本中发起的跨域请求。比如,使用 XMLHttpRequest 对象和Fetch发起 HTTP 请求就必须遵守同源策略。Web 应用程序通过 XMLHttpRequest 对象或Fetch能且只能向同域名的资源发起 HTTP 请求,而不能向任何其它域名发起请求。不允许跨域访问并非是浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。

解决

jsonp(JSON with Padding)

  • 原理

    首先在http:\\127.0.0.1:8000\test\下有如下返回字符串‘ok’的视图:

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

    服务端

    然后在http:\\127.0.0.1:8001\域下直接用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>

    浏览器端

    看效果(这里我使用的是火狐浏览器,提示更直观):

    上面有提到过<script>等标签可以加载跨域资源,我们试一下直接让script标签的src属性指向http:\\127.0.0.1:8000\test\:

    浏览器端

    会发现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方法获取到了。

  • 简单请求&复杂请求

    • 条件
      、请求方式:HEAD、GET、POST
      、请求头信息:
      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、支持跨域,复杂请求。

      、“预检”请求时,在服务端设置允许的请求方式:Access-Control-Request-Method
      、“预检”请求时,在服务端设置允许的响应头:Access-Control-Request-Headers
      、“预检”缓存时间,服务器设置响应头: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

解决跨域问题-jsonp&cors的更多相关文章

  1. Nginx解决跨域问题(CORS)

    跨域 解决跨域问题一般有两种思路: CORS 在后端服务器设置 HTTP 响应头,把你需要运行访问的域名加入加入 Access-Control-Allow-Origin中. jsonp 把后端根据请求 ...

  2. c# WebApi之解决跨域问题:Cors

    什么是跨域问题 出于安全考虑,浏览器会限制脚本中发起的跨站请求,浏览器要求JavaScript或Cookie只能访问同域下的内容.由于这个原因,我们不同站点之间的数据访问会被拒绝. Cors解决跨域问 ...

  3. 跨域访问JSONP CORS

    一.JSONP 常用的Jquery框架支持jsonp方式请求,该方式只支持GET方法,传参大小有限,而且需要后台根据jsonp的请求方式进行封装结果返回. 其中参数jsonp默认为callback,j ...

  4. Java 后端彻底解决跨域问题(CORS)

    接口调用出现跨域问题时,浏览器会报如下提示 XMLHttpRequest cannot load xxx. Request header field Authorization is not allo ...

  5. 学习AJAX必知必会(4)~同源策略、解决跨域问题(JSONP、CORS)

    一.同源策略(Same-Origin Policy),是浏览器的一种安全策略. 1.同源(即url相同):协议.域名.端口号 必须完全相同.(请求是来自同一个服务) 2.跨域:违背了同源策略,即跨域. ...

  6. 一步一步学习SignalR进行实时通信_3_通过CORS解决跨域

    原文:一步一步学习SignalR进行实时通信_3_通过CORS解决跨域 一步一步学习SignalR进行实时通信\_3_通过CORS解决跨域 SignalR 一步一步学习SignalR进行实时通信_3_ ...

  7. 跨域Ajax -- jsonp和cors

    跨域Ajax - jsonp - cors 参考博客: http://www.cnblogs.com/wupeiqi/articles/5703697.html http://www.cnblogs. ...

  8. jquery跨域解决方案JSONP

    1.在互联网中我们的计算机是通过IP来定位的,但是IP比较难记忆,因此通过domain name(域名)来取代IP 2.什么是跨域? (1)默认浏览器为了安全问题,禁止了xmlhttprequest跨 ...

  9. ajax解决跨域

    http://www.cnblogs.com/sunxucool/p/3433992.html 为什么会出现跨域跨域问题来源于JavaScript的同源策略,即只有 协议+主机名+端口号 (如存在)相 ...

随机推荐

  1. 6. 从Encoder-Decoder(Seq2Seq)理解Attention的本质

    1. 语言模型 2. Attention Is All You Need(Transformer)算法原理解析 3. ELMo算法原理解析 4. OpenAI GPT算法原理解析 5. BERT算法原 ...

  2. 判断当前viewcontroller是push还是present的方式显示的

    网上的姿势,反正我用着不管用 最正确的姿势 NSArray *viewcontrollers = self.navigationController.viewControllers; if (view ...

  3. [微信小程序] 微信小程序开发初步探索

    1.开发文档 https://developers.weixin.qq.com/miniprogram/dev/ app.json配置:https://developers.weixin.qq.com ...

  4. 消息中间件系列二:RabbitMQ入门(基本概念、RabbitMQ的安装和运行)

    一.基本概念 1. AMQP AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议.支持不同语言和不同的产品 2. 生产者 ...

  5. macOS Sierra 如何卸载.net core 版本

    由于目前没有找到一个合适的办法,将本机.NET Core的版本升级到1.1,故只有先卸载再安装最新版本了. 卸载脚本链接为:https://github.com/dotnet/cli/blob/rel ...

  6. deep learning 以及deep learning 常用模型和方法

    首先为什么会有Deep learning,我们得到一个结论就是Deep learning需要多层来获得更抽象的特征表达. 1.Deep learning与Neural Network 深度学习是机器学 ...

  7. SimpleDateFormat 出现错误 Call requires API level 24 (current min is 15)

    这个故事是这样的 今天写打卡时间的时候需要获取一下当前时间,然后我就写了一个这个 SimpleDateFormat sDF =   new SimpleDateFormat("yyyy-MM ...

  8. 使用redis接管cookie

    class RedisCookie { // 默认配置名称(使用load_config加载) private $_default_config_path = 'package/cache/redis_ ...

  9. 把项目挂载到composer上

    1.打开composer的安装包列表网站,点击submit 2.把刚才初始化了composer的项目push到github上(至于怎么push,最简单就是用git了) 3.然后把github的网址复制 ...

  10. STL之pair对组

    #include<iostream> #include<algorithm> #include<cstring> #include<cstdlib> u ...