zoukankan      html  css  js  c++  java
  • 同源策略和Jsonp、CORS跨域

    一、同源策略

      同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

      同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问

    1、跨域示例

    端口8006:点击按钮跨域访问

    ############# urls #############
    from django.contrib import admin
    from django.urls import path
    from app01 import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.index),
        path('service/', views.service),
    ]
    
    ############# 视图 #############
    from django.shortcuts import render, HttpResponse
    # Create your views here.
    
    def index(request):
        return render(request, "index.html")
    
    def service(request):
        return HttpResponse("技师alex")
    
    ############# index.html #############
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h3>INDEX</h3>
    <button class="get_service">洗剪吹</button>
    
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    <script>
        $(".get_service").click(function () {
            $.ajax({
                 // url: "/service/",
                url: "http://127.0.0.1:8008/service/",   // 跨域访问
                success:function(data){
                    console.log(data);
                }
            })
        })
    </script>
    </body>
    </html>
    

    端口8008:点击按钮同域访问

    ############# urls #############
    from django.contrib import admin
    from django.urls import path
    from app01 import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.index),
        path('service/', views.service),
    ]
    
    ############# 视图 #############
    from django.shortcuts import render, HttpResponse
    # Create your views here.
    
    def index(request):
        return render(request, "index.html")
    
    def service(request):
        print("技师egon")
        return HttpResponse("技师alex")
    
    ############# index.html #############
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h3>INDEX</h3>
    <button class="get_service">洗剪吹</button>
    
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    <script>
        $(".get_service").click(function () {
            $.ajax({
                 url: "/service/",
                success:function(data){
                    console.log(data);
                }
            })
        })
    </script>
    </body>
    </html>
    

    测试验证:

    (1)跨域访问报错

    (2)同域访问正常

    (3)跨域访问虽然被拦截了,但是目标服务视图函数有执行打印。说明跨域时,请求可以发送过去,但是返回给浏览器时被拦截。

      

      项目2中的访问已经发生了,说明是浏览器对非同源请求返回的结果做了拦截。

    2、script标签的跨域特性

    思考:这算怎么回事?

    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    

    script标签本身就可以访问其它域的资源,不受浏览器同源策略的限制,可以通过在页面动态创建script标签。

    src:该属性指定外部JavaScript文件的地址,可以跨域。如果指定了该属性,那么script标签中的内容就会被忽略。

    二、Jsonp

      JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

    1、借助script标签实现跨域请求

    (1)发送跨域请求并处理

    端口8006发送跨域请求:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h3>INDEX</h3>
    <button class="get_service">洗剪吹</button>
    <script>
        function egon(arg) {
            console.log(arg);
            console.log(typeof arg);
            var data = JSON.parse(arg);
            console.log(data);
            console.log(typeof data);
        }
    </script>
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    <script src="http://127.0.0.1:8008/service/"></script>
    
    <script>
        // $(".get_service").click(function () {
        //     $.ajax({
        //          // url: "http://127.0.0.1:8006/service/",
        //         url: "http://127.0.0.1:8008/service/",
        //         success:function(data){
        //             console.log(data);
        //         }
        //     })
        // })
    </script>
    </body>
    </html>
    

    端口8008处理请求返回数据:

    def service(request):
        print("技师egon")
        info = {"name": "egon", "age": 34, "price": 200}
        return HttpResponse("egon('%s')" % json.dumps(info))
    

    (2)显示效果

    访问127.0.0.1:8006/index,浏览器控制台输出如下:

    访问127.0.0.1:8008/index,并点击按钮,浏览器控制台输出如下:

    (3)示例现象总结

      这其实就是JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。

      将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。

      一般情况下,我们希望这个script标签能够动态的调用,而不是像上面因为固定在html里面所以没等页面显示就执行了,很不灵活。我们可以通过javascript动态的创建script标签,这样我们就可以灵活调用远程服务了。

    2、利用javascript动态创建script标签实现跨域请求

    <body>
    <h3>INDEX</h3>
    <button class="get_service">洗剪吹</button>
    
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    <script>
        function egon(arg) {
            console.log(arg);
            console.log(typeof arg);
            var data = JSON.parse(arg);
            console.log(data);
            console.log(typeof data);
        }
    
        function get_jsonp_data(url) {
            // DOM操作创建一个script标签
            var ele_script = $("<script>");
            ele_script.attr("src", url);
            ele_script.attr("class", "jsonp");  // 给script标签添加class="jsonp"属性
            $("body").append(ele_script);
            $("#jsonp").remove();   // 删除刚刚创建的script标签
        }
    
        $(".get_service").click(function () {
            get_jsonp_data("http://127.0.0.1:8008/service/");
        })
    </script>
    </body>
    
    注意:

    (1)客户端函数名必须和服务端函数名保持一致,缺乏灵活性

      function egon(arg){}函数名必须和8008的service视图中HttpResponse("egon('%s')" % json.dumps(info))保持一致

    def service(request):
        print("技师egon")
        info = {"name": "egon", "age": 34, "price": 200}
        return HttpResponse("egon('%s')" % json.dumps(info))
    

    (2) 在客户端定义的回调函数的函数名传送给服务端,增加灵活性

    8006端口index.html修改点击事件:

    $(".get_service").click(function () {
        get_jsonp_data("http://127.0.0.1:8008/service/?callbacks=alex");
    })
    

      习惯上把这个get方法提交的数据命名为callbacks告诉其他人这是回调函数名。这里将函数名修改为了alex,对应的函数名也要修改为相同名称:

    function alex(arg) {
            console.log(arg);
            console.log(typeof arg);
            var data = JSON.parse(arg);
            console.log(data);
            console.log(typeof data);
        }
    

    8008端口视图修改如下:

    def service(request):
        print("技师egon")
        func = request.GET.get("callbacks")
        print(func)
        info = {"name": "egon", "age": 34, "price": 200}
        return HttpResponse("%s('%s')" % (func, json.dumps(info)))
    

    (3)通过DOM操作创建并删除script标签

    function get_jsonp_data(url) {
        // DOM操作创建一个script标签
        var ele_script = $("<script>");
        ele_script.attr("src", url);
        ele_script.attr("class", "jsonp");  // 给script标签添加class="jsonp"属性
        $("body").append(ele_script);
        $("#jsonp").remove();   // 删除刚刚创建的script标签
    }
    

      这样点击按钮,生成的script标签会立马删除,但是跨域请求已经发送完成。

    三、jQuery对JSONP的实现

    1、getJSON

    jQuery框架也当然支持JSONP,可以使用$.getJSON(url,[data],[callback])方法.

    8001的html改为:

    <button onclick="f()">洗剪吹</button>
    
    <script>
      function f(){
        $.getJSON("http://127.0.0.1:8008/service/?callbacks=?",function(arg){
          alert("hello"+arg)
        });
      }
    </script>
    

    8002的views不改动。

      结果是一样的,要注意的是在url的后面必须添加一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个问号是内部自动生成的一个回调函数名。

          此外,如果说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?我们可以使用$.ajax方法来实现

    2、$.ajax(用jquery封装,利用script标签模拟ajax请求)

    8006的index.html改为:

    <script>
        function alex(arg) {
            console.log(arg);
            console.log(typeof arg);
            var data = JSON.parse(arg);
            console.log(data);
            console.log(typeof data);
        }
    
        $(".get_service").click(function () {
            $.ajax({
                url: "http://127.0.0.1:8008/service/",
                type: "get",
                dataType: 'jsonp',   // 伪ajax, 基于script标签创建
                jsonp: "callbacks",  // 请求的键
                jsonpCallback: "alex"   // 请求的值,具体函数的名字
            })
        })
    </script>
    

    注意:

    (1)利用script标签发送请求,因此只能是get请求。 JSONP一定是GET请求

    (2)将ajax请求中的dataType属性设置为“jsonp”,jsonp是专门用来解决跨域访问而诞生的。

    (3)8008的views不需要改动。

    (4)jsonp: 'callbacks'就是定义一个存放回调函数的键,jsonpCallback是前端定义好的回调函数方法名'alex',server端接受callback键对应值后就可以在其中填充数据打包返回了;

    通过回调函数来处理(简化形式)

    <script>
        $(".get_service").click(function () {
            $.ajax({
                url: "http://127.0.0.1:8008/service/",
                type: "get",
                dataType: 'jsonp',   // 必须有,告诉server,这次访问要的是一个jsonp的结果。
                jsonp: "callbacks",   //jQuery帮助随机生成的:callbacks="随机函数名"
                // jsonpCallback: "alex"   // 请求的值,具体函数的名字
                success: function (data) {
                    console.log(data);
                }
            })
        })
    </script>
    

     注意:

    (1)jsonpCallback参数可以不定义,jquery会自动定义一个随机名发过去,那前端就得用回调函数来处理对应数据了。利用jQuery可以很方便的实现JSONP来进行跨域访问。

    (2)随机生成的函数名如下所示:

    3、应用示例

    有乱码页面如下所示:

      

    获取该页面数据处理如下:

    body>
    <h3>INDEX</h3>
    <button class="get_service">洗剪吹</button>
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    
    <script>
        // 应用
        $(".get_service").click(function () {
            $.ajax({
                url: "http://www.jxntv.cn/data/jmd-jxtv2.html",
                type: "get",
                dataType: 'jsonp',   // 必须有,告诉server,这次访问要的是一个jsonp的结果。
                jsonp: "callbacks",   //jQuery帮助随机生成的:callbacks="随机函数名"
                jsonpCallback: "list",
                success: function (data) {
                    console.log(data);
                    
                    // 处理数据
                    var html = "";
                    $.each(data.data, function (index, weekday) {
                        console.log(weekday);   // { week:"周日", list: Array(19) }
                        // 构建p标签,取到周一到周日
                        html += "<ul>"+weekday.week+"</ul>";
    
                        // 循环处理list: Array(19), 列表中每一条数据格式:{time: "0030", name: "通宵剧场六集连播", link: "http://www.jxntv.cn/live/jxtv2.shtml"}
                        $.each(weekday.list, function (j, show) {   // show是一个个字典
                            html += "<li><a href="+show.link+">"+show.name+"</a>"+
                                "  <span>"+show.time+"</span></li>";
                        })
                    });
    
                    // 将p标签添加到页面body中
                    $("body").append(html);
                }
            })
        })
    </script>
    </body>
    

    点击按钮后,页面显示如下:

      

    四、CORS(跨域资源共享)

      随着技术的发展,现在的浏览器可以支持主动设置从而允许跨域请求,即:跨域资源共享(CORS,Cross-Origin Resource Sharing),其本质是设置响应头,使得浏览器允许跨域请求。

      跨域请求分为两种:简单请求、复杂请求。

    1、简介

      CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

      整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

      因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

    2、浏览器同源策略

      跨域主要是由于浏览器的同源策略导致,也就是说浏览器会阻止非同源的请求

      定义非同源:域名不同 或 端口不同。

      浏览器只阻止表单及ajax请求,并不会阻止src请求(cdn、图片等src请求)

    3、cors示例

    (1)8006端口index.html,执行跨域请求

    <button class="get_service">洗剪吹</button>
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    
    <script>
        // cors
        $('.get_service').click(function () {
            $.ajax({
                url:'http://127.0.0.1:8008/service/',
                success:function (data) {
                    console.log(data);
                }
            })
        })
    </script>
    

    (2)点击按钮后,浏览器报错:(Firefox浏览器报错信息已经自动翻译为中文)

    (3)在服务端8008视图函数中添加响应头:

    def service(request):
        info = {"name": "egon", "age": 34, "price": 200}
        response = HttpResponse(json.dumps(info))
    
        # 添加响应头,告诉浏览器不要拦截了
        response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8006"
        
        # 都不拦截了,任何人都可以访问
        # response['Access-Control-Allow-Origin'] = "*"
        return response
    

      添加后,点击按钮跨域访问不再报错:

      

    4、两种跨域请求

      浏览器将CORS请求分成两类:简单请求(simple request)非简单请求(not-so-simple request)

    (1)简单请求与非简单请求区分标准

      只要同时满足以下两大条件,就属于简单请求

    (1) 请求方法是以下三种方法之一:
    HEAD
    GET
    POST
    (2)HTTP的头信息不超出以下几种字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
    

      凡是不同时满足上面两个条件,就属于非简单请求

    (2)浏览器对两种请求的处理

      浏览器对这两种请求的处理,是不一样的。

    * 简单请求和非简单请求的区别?
    
       简单请求:一次请求
       非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
    * 关于“预检”
    
    - 请求方式:OPTIONS
    - “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
    - 如何“预检”
         => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
            Access-Control-Request-Method
         => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
            Access-Control-Request-Headers
    

      支持跨域,简单请求

      服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'

      支持跨域,复杂请求

      由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。

    • “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
    • “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers

    5、用JSONP解决跨域

      jsonp的实现原理是根据浏览器不阻止src请求(cdn、图片等)来入手实现的。

      json虽然能解决跨域,但是只能解决get请求的跨域。并且实现起来需要前后端交互比较多。

    (1)后端view.py

    from django.shortcuts import render
    from django.http import HttpResponse
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    # Create your views here.
    
    
    class DemoView(APIView):
        def get(self, request):
            res = "handlerResponse('跨域测试')"
            return HttpResponse(res)

    (2)前端demo.html

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Title</title>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script>
        <script>
            function handlerResponse(data) {   // 定义handlerResponse函数,避免找不到
                console.log(data)
            }
        </script>
        <!-- 使用src跨域 -->
        <script src="http://127.0.0.1:8000/demo/"></script>
    </head>
    <body>
    <div id="app">
    
    </div>
    <script>
        const app = new Vue({
            el: "#app",
            mounted(){   // 钩子函数
            }
        })
    </script>
    
    </body>
    </html>

    (3)显示效果

      

    6、用中间件处理跨域问题(通用方法)

      通过添加响应头告诉浏览器不用拦截。

      settings.py:

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'api.cors.CORSMiddleware'
    ]
    

      创建该文件  应用名/cors.py:

    from django.utils.deprecation import MiddlewareMixin
    
    class CORSMiddleware(MiddlewareMixin):
        """自定义中间件"""
        def process_response(self, request, response):
            # 添加响应头
    
            # 允许你的域名来获取我的数据
            response['Access-Control-Allow-Origin'] = "*"
            # 允许你携带Content-Type请求头,这里不能写*
            # response['Access-Control-Allow-Headers'] = "Content-Type"
            # 允许你发送GET/POST/DELETE/PUT
            # response['Access-Control-Allow-Methods'] = "GET, POST"
    
            if request.method == "OPTIONS":
                response["Access-Control-Allow-Header"] = "Content-Type"
            return response 

    五、更多参考博客

    Django-Ajax

    Python开发【第十六篇】:AJAX全套

    跨域

  • 相关阅读:
    一个好的时间函数
    Codeforces 785E. Anton and Permutation
    Codeforces 785 D. Anton and School
    Codeforces 510 E. Fox And Dinner
    Codeforces 242 E. XOR on Segment
    Codeforces 629 E. Famil Door and Roads
    Codeforces 600E. Lomsat gelral(Dsu on tree学习)
    Codeforces 438D The Child and Sequence
    Codeforces 729E Subordinates
    【ATcoder】D
  • 原文地址:https://www.cnblogs.com/xiugeng/p/9484923.html
Copyright © 2011-2022 走看看