同源策略与跨域问题解决

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同源策略与跨域问题解决相关的知识,希望对你有一定的参考价值。

参考技术A

如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的

举个例子:

下表给出了相对 http://a.bbb.com/dir/page.html 同源检测的示例:

由同源策略导致的的AJAX请求失败

例如:页面路径为: http://127.0.0.1:8848/fileTest/test.html

后台接口地址为: http://localhost:8080/sayHello

产生的结果:

此即为跨域请求失败

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了AJAX只能 同源 使用的限制。

在此之前,需要知道 简单请求、复杂请求

简单请求:

某些请求不会触发 CORS 预检请求 。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“简单请求”:

复杂请求:

与前述简单请求不同,“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。当请求满足下述任一条件时,即应首先发送预检请求:

若需要跨域,则需要在请求头里添加 Origin 字段

请求至后台若符合后台请求规则则可成功请求,并且后台会返回相应的规则

当请求为复杂请求时,浏览器会在请求前先发送一个 OPTIONS 请求

OPTIONS 是一个预检请求,与简单请求不同的是它会额外携带两个参数: Access-Control-Request-Method :该次请求的请求方式 Access-Control-Request-Headers :该次请求的自定义请求头字段

OPTIONS 发送至后台若后台成功响应,则继续发送复杂请求,若响应失败则不会发送复杂请求

继续发送复杂请求

(1)、OPTIONS不会携带body若后台方法上使用@RequestBody注解,则会报错:

例如:

此时需要在@RequestBody后添加参数required=false,允许body为空

即可解决该问题

(2)、OPTIONS请求不应被过多的发送,因为它也是一个http请求也会占用一部分资源,相应的一种解决方法

在响应中添加缓存字段,以减少OPTIONS请求

(1)、在Java web工程中加如下列过滤器即可

(2)、在nginx中配置

使用反向代理工具解决跨域问题:如 Nginx node.js

以Nginx为例:

在Nginx的配置文件中加入:

启动Nginx再以

http://127.0.0.1/fileTest/test.html 地址访问页面

http://127.0.0.1/sayHello 访问后台接口

发现可正常请求

上述以

http://127.0.0.1/fileTest/test.html 地址访问页面

http://127.0.0.1/sayHello 访问后台接口

的方式遵循了,浏览器的同源策略自然不存在跨域

Nginx代理了页面请求和接口请求,所以页面请求和接口请求都在Nginx源内由Nginx去完成页面请求和接口的请求即: http://127.0.0.1:80 内

所以浏览器访问页面和接口都是以 http://127.0.0.1:80 来进行访问的,满足了同源策略自然不存在跨域之说

图解:

跨域:

Nginx代理,满足同源策略

Nginx反向代理其实是一种欺骗浏览器的方法

[1] http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html 浏览器同源政策及其规避方法

[2] http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html XMLHttpRequest Level 2 使用指南

[3] http://www.ruanyifeng.com/blog/2016/04/cors.html 跨域资源共享 CORS 详解

[4​] https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS HTTP访问控制(CORS)

同源策略与跨域请求

一、同源策略

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

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

jsonp(jsonpadding)

之前发ajax的时候都是在自己给自己的当前的项目下发

现在我们来实现跨域发。给别人的项目发数据,

创建两个项目,先来测试一下

项目一:Http://127.0.0.1:8006

views.py

from django.shortcuts import render,HttpResponse

def index(request):
    return render(request,"index.html")

def service(request):
    return HttpResponse("Project 1")

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>

<h3>INDEX</h3>

<button class="get_service">Project 1</button>

<script src="/static/jquery-3.3.1.js"></script>
<script>
    $(".get_service").click(function () {
        alert(123);
        $.ajax({
            url: "http://127.0.0.1:8008/service/", 
            success: function (data) {
                console.log(data)
            }
        })
    });

</script>

</body>
</html>

项目二:Http://127.0.0.1:8006

views.py

from django.shortcuts import render,HttpResponse

def index(request):

    return render(request,"index.html")

def service(request):

    return HttpResponse("Project 2")

访问项目一的index界面

技术图片

 当点击Project 1 按钮时本应该跳转到项目二的service路径下给我们回一个响应,但此时报了一个错误:

技术图片

 这是因为同源策略给限制了,这是游览器给我们报的一个错。我们可以看到弹出了alert框,说明去项目二拿到了数据,只是浏览器不给我们显示。

注意:a标签,form,img标签,引用cdn的css等也属于跨域(跨不同的域拿过来文件来使用),不是所有的请求都给做跨域,(为什么要进行跨域呢?因为我想用人家的数据,所以得去别人的url中去拿,借助script标签)

只有发ajax的时候给拦截了,所以要解决的问题只是针对ajax请求能够实现跨域请求

二、解决同源策略的两种方法

1、基于jsonp实现跨域请求

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

jsonp是json用来跨域的一个东西。原理是通过script标签的跨域特性来绕过同源策略。

项目一:

index.html

<script>    $(".get_service").click(function () {
        $.ajax({
            url: "http://127.0.0.1:8008/service/",
            type: "get",
            dataType: "jsonp",     // 必须有,告诉service这次访问的是一个jsonp的结果
            jsonp: callbacks,
            //jsonpCallback:"alex",
            success: function (data) {  //ajax拿到返回值(随机字符串)之后立即执行函数
                console.log(data)
            }
        })
    });
</script>

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

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

注意 JSONP一定是GET请求

项目二:

views.py

from django.shortcuts import render,HttpResponse
import json
def service(request):  # jsonp

    func=request.GET.get("callbacks")
    print("func",func)    #func jQuery33105914359147116615_1589618121318
    info={"name":"egon","age":34,"price":200}
    return HttpResponse("%s (‘%s‘)"%(func,json.dumps(info)))

技术图片 

 再次访问项目一的index界面,点击Project 1按钮在控制台上就展现了我们向项目二所拿到的数据了。

2、基于cors实现跨域请求(比较常见的方式,简单)

只需在响应体中对 Access-Control-Allow-Origin进行设置

项目一:

index.html 

<script src="/static/jquery-3.3.1.js"></script>
<script>    $(".get_service").click(function () {

        $.ajax({
            url: "http://127.0.0.1:8008/service/",
            success: function (data) {
                console.log(data)
            }
        })
    });

</script>

 在最开始我们就用上面的形式对项目二的service进行访问,但被浏览器拦截了,接下来我们只需在响应体中对 Access-Control-Allow-Origin进行设置就能够

实现跨域请求。

项目二:

view.py

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

而且我们发现这种方式实现的跨域比基于jsonp实现跨域请求较为简单!

三、应用

// 跨域请求实例
    $(".get_service").click(function () {

        $.ajax({
            url:"http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403",
             dataType: ‘jsonp‘,
             jsonp: ‘callback‘,
             jsonpCallback: ‘list‘,
             success:function (data) {
                 console.log(data.data);   //  [{},{},{},{},{},{}]
                 week_list=data.data;
                 
                 $.each(week_list,function (i,j) {
                     console.log(i,j);  // 1 {week: "周一", list: Array(19)}
                     s="<p>"+j.week+"列表</p>";
                     $(".show_list").append(s);

                     $.each(j.list,function (k,v) {  // {time: "0030", name: "通宵剧场六集连播", link: "http://www.jxntv.cn/live/jxtv2.shtml"}
                          a="<p><a href=‘"+v.link+"‘>"+v.name+"</a></p>";
                          $(".show_list").append(a);
                     })
                 })
                 
             }
        })

    })

附:

技术图片
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>

<h3>INDEX</h3>

<button class="get_service">Project 1</button>


<script src="/static/jquery-3.3.1.js"></script>
<script>

    $(".get_service-0").click(function () {
        alert(123);
        $.ajax({

            url: "http://127.0.0.1:8008/service/",
            success: function (data) {
                console.log(data)
            }
        })
    });

    function alex(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) {
        var ele_script = $("<script>");
        <!--创建scr标签-->
        ele_script.attr("src", url);
        <!--为标签添加src属性-->
        ele_script.attr("id", "jsonp");
        <!--为标签添加id属性-->
        $("body").append(ele_script);
        <!--把标签添加到body中-->
        $("#jsonp").remove()
    }

    <!--标签添加到body中后就已经执行了,执行之后删除标签-->

    $(".get_service").click(function () {

        $.ajax({
            url: "http://127.0.0.1:8008/service/",
            success: function (data) {
                console.log(data)
            }
        })
    });


    $(".get_service-2").click(function () {

        get_jsonp_data("http://127.0.0.1:8008/service/?callbacks=alex")

    });

    // 终极形式

    $(".get_service-3").click(function () {

        $.ajax({

            url: "http://127.0.0.1:8008/service/",
            type: "get",
            dataType: "jsonp",     // 伪造ajax  基于script
            jsonp: callbacks,
            //jsonpCallback:"alex",
            success: function (data) {  //ajax拿到返回值(随机字符串)之后立即执行函数
                console.log(data)
            }
        })
    });

    // 应用

    $(".get_service-4").click(function () {

        $.ajax({
            url: "http://www.jxntv.cn/data/jmd-jxtv2.html",
            type: "get",
            dataType: "jsonp",     // 伪造ajax  基于script
            jsonp: callbacks,
            jsonpCallback: "list",
            success: function (data) {
                //console.log(data.data);

                var html = "";
                $.each(data.data, function (index, weekday) {
                    console.log(weekday); // {week: "周一", list: Array(19)}

                    html += <p> + weekday.week + </p>;

                    $.each(weekday.list, function (j, show) {
                        html += <p><a href= + show.link + > + show.name + </a></p>
                    })
                });
                $("body").append(html)
            }
        })
    })
</script>

</body>
</html>
实现跨域请求的流程

参考1

参考2

 

以上是关于同源策略与跨域问题解决的主要内容,如果未能解决你的问题,请参考以下文章

同源策略与跨域请求

同源策略与跨域请求

同源策略与跨域访问

浏览器同源策略与跨域出现原因

同源策略与跨域问题

AJAX 与跨域通信:AJAX 与同源策略