前端跨域难,学会跨域不再低调

1,061 阅读7分钟

学会跨域不再低调

- 跨域产生的原因

跨域产生的原因是同源策略的存在。 同源策略是浏览器为了保护网页的安全而设置的一种安全保护手段,在没有明确的授权下,不同的协议、域名、和端口的客户端脚本不能够读写对方的资源。有时为了获取不同网页下的数据会进行一系列地操作,所以产生了跨域。

- 常用方法

  • 常用方法
  1. 通过jsonp跨域(script脚本)
  2. jquery的ajax跨域(jsonp类型)
  3. 跨域资源共享(cors,后台)
  4. nginx代理跨域(php代理)
  5. node正向代理
  • 其它方法
  1. WebSocket协议跨域
  2. documenet.domain+iframe跨域
  3. window.name+iframe跨域
  4. location.hash+iframe跨域
  5. postMessage跨域

- 一.jsonP实现跨域

jsonp实现跨域:script标签无同源限制,但仅限于get请求

1.为什么浏览器对script标签不限制同源策略?

​ 首先,我们来了解一下,浏览器是对什么有请求同源限制。 浏览器只对XHR(XML Http Request)请求有同源限制,而对script标签,link标签,img标签没有同源限制。因此这三种标签可以获取到不同域下的数据,而script标签就可以获取到json数据。jsonp就以此为基础,需要向第三方请求时,把含有回调函数的请求放在src标签中。添加到当前网页中,就会立即请求数据。

2.jsonp的原理

​ 当前站点提供一个回调函数来接收数据(函数名可约定,或通过地址参数传递),而第三方网站产生的响应为json数据的包装(故称之为jsonp,即json padding),形如:callback({“name”:“fegnjie”,“age”:“18”})。一旦开始将包含回调函数参数script标签添加到当前页面,则会立即向第三方网站发起请求,第三方网站响应成功后,当前浏览器会将json对象作为参数传递到callback回调函数中,因此完成了跨域请求。

3.使用流程

  1. 设置一个回调函数,并将其提前创建
  2. 创建一个src中包含回调函数的script标签,将标签插入到当前页面。

url语法:https://需要访问的网址/路径?必要的参数&callback=自定义的回调函数名称

4.开始实验

准备工作做好,找一个远程的cdn,把它放在网页的head标签内。当前的实验需要jquery的支持!使用原生js也没有问题。

<!--client.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>client1</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是client1</h1>
<button id="myBtn">向百度发起请求</button>

<script>
    $("#myBtn").click(function () {
        $.get("https://www.baidu.com/", function (data) {
            console.log(data)
        });         // 我们最喜欢的百度首页。
    });
</script>

</body>
</html>

显然,跨域的存在,让我们的ajax无法得手。难道就这样 ,当然有解决方案。

image-20211114192609631.png

现在有一个方案,就是使用script脚本的漏洞,它的src是可以放置其它域的链接的,而我们可以动态地创建script标签,并把需要访问的网页连接放在src。这样也许可行。

   // 监听点击事件
    $("#myBtn").click(function () {
        myScript("https://www.baidu.com/");      // 测试一下。
       // $.get("https://www.baidu.com/", function (data) {         // 暂时不用
       //     console.log(data)
       //  });
    });

   // 创建script标签
    function myScript(src){
        let newNode = $("script").attr("src",src);
        $('body').append(newNode);
    }

现在再来点击一下按钮,感觉写的jquery语法不行,太差了,换一下。下面可以避免重复向当前网页添加同一个元素。不过获取数据,也确实是因为把script添加到当前页面,才成功跨域。

   // 创建script标签
   function myScript(src){
        let newNode = $(`<script src="${src}" > <\/script>`);
        var hasScript = false;
        $('body > script').filter((index,item)=>{         // 只有filter可以过滤遍历
            if(item.src === src){                       
                            // 而forEach, some, every都不能当作遍历Jquery节点的函数
                hasScript = true;
                console.log("网页中已经有这个节点了")
            }
        });
        if(!hasScript){               // 不允许出现多个重复的script跳转标签
            $('body').append(newNode);
        }
    }

image-20211114201515246.png

已经可以向当前页面添加script标签,但好像没什么作用。这是因为要在script的src属性上添加一个回调函数。这个参数名称可自定义,与后台无关,并且一定要提前创建,而不能等待页面加载完成才创建。

url语法:https://需要访问的网址/路径?必要的参数&callback=自定义的回调函数名称

// 设置回调函数, 这个回调函数要提前创建,不能在页面加载完毕后再创建。
    // 回调参数名称与url中的参数一致, 这个回调函数名称只与当前页面有关,而后台是没有这个参数的。
    function hello(json) {
        console.log(json)
    }

不过,现在我却不需要ajax,这是为什么呢?其实当前的方法是使用不上异步请求的,当我们将script标签添加到当前页面,已经可以获取 jsonp,(json数据包)。但百度的效果看不出来,因为返回的是一个页面。

image-20211114202809921.png

现在我要替换一个网站,大家看看就行,就。。。

        // 监听事件
        $("#myBtn").click(function () {
          // 酷*官网的获取json数据的请求头。
 /*https://searchrecommend.kugou.com/get/complex?callback=jQuery112402531924339219609
 _1636893306420&word=love&_=1636893306422*/

          myScript("https://searchrecommend.kugou.com/get/complex?callback=hello&word=love&_=1636893306422");
        });

image-20211114212210257.png 控制台也通过回调函数获取到信息

image-20211114212338391.png 其实,细心地观察过原url后,酷*官网也是使用了这种方法去跨域请求数据的,也就是殊途同归,

5.实现代码

最后写上代码

<!--jquery版本的jsonp跨域实现-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>client1</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是client1</h1>
<button id="myBtn">向酷*发起请求</button>

<script>

    // 设置回调函数, 这个回调函数要提前创建,不能在页面加载完毕后再创建。
    // 回调参数名称与url中的参数一致, 这个回调函数名称只与当前页面有关,而后台是没有这个参数的。
    function hello(json) {
        console.log(json)
    }
    $(function() {

        // 监听事件
        $("#myBtn").click(function () {
            // 酷*官网的获取json数据的请求头。
            /*https://searchrecommend.kugou.com/get/complex?callback=jQuery112402531924339219609_1636893306420&word=love&_=1636893306422*/

            myScript("https://searchrecommend.kugou.com/get/complex?callback=hello&word=love&_=1636893306422");
        });


        // 创建script标签
        function myScript(src) {
            let newNode = $(`<script src="${src}" > <\/script>`);
            var hasScript = false;
            $('body > script').filter((index, item) => {         // 只有filter可以过渡
                if (item.src === src) {                         // 而forEach, some, every都不能当作遍历Jquery节点的函数
                    hasScript = true;
                    console.log("网页中已经有")
                }
            });
            if (!hasScript) {               // 不允许出现多个重复的script跳转标签
                $('body').append(newNode);
            }
        }
    });
</script>

</body>
</html>
<!--原生js版本的jsonp跨域实现-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>client1</title>
</head>
<body>
<h1>我是client1</h1>
<button id="myBtn">向酷*发起请求</button>

<script>

    // 设置回调函数, 这个回调函数要提前创建,不能在页面加载完毕后再创建。
    // 回调参数名称与url中的参数一致, 这个回调函数名称只与当前页面有关,而后台是没有这个参数的。
    function hello(json) {
        console.log(json)
    }
    window.onload = function(){

        // 监听事件
        document.querySelector("#myBtn").onclick = function () {
            // 酷*官网的获取json数据的请求头。
            /*https://searchrecommend.kugou.com/get/complex?callback=jQuery112402531924339219609_1636893306420&word=love&_=1636893306422*/

            myScript("https://searchrecommend.kugou.com/get/complex?callback=hello&word=love&_=1636893306422");
        };


        // 创建script标签
        function myScript(src) {
            let newNode = document.createElement("script");
            newNode.src = src;
            document.body.appendChild(newNode);
        }
    };
</script>

</body>
</html>

相关文章:

跨域的气种方法想吃可爱多的博客CSDN博客_解决跨域的三种方法

- 二.ajax请求实现跨域

1.ajax实现的原理

原生ajax首先不谈兼容性,而同源策略就是因为有它的存在,所以原生的ajax是行不通的。

但jquery已经帮我们封装ajax中,已经有jsonp类型,它可以帮助我们直接获取数据。

这里的链接已经不需要回调函数,需要把callback回调参数去除

  $("#myBtn").click(function () {
        $.ajax({
            url: 'https://searchrecommend.kugou.com/get/complex?word=love&_=1636893306422',
            dataType: "jsonp",
            jsonp: "callback",
            success: function (data) {
                console.log(data)
            }
        })
    })

image-20211114220502874.png 结果同上面一样,引入一个jquery,确实解决了不少问题。

2.实现代码

<!--jquery版本ajax的jsonp跨域实现-->
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>jquery版本ajax的jsonp跨域实现</title>
   <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>

</head>
<body>
<h1>我是jquery版本ajax的jsonp跨域实现</h1>
<button id="myBtn">向酷*发起请求</button>

<script>

   $("#myBtn").click(function () {
       $.ajax({
           url: 'https://searchrecommend.kugou.com/get/complex?word=love&_=1636893306422',
           dataType: "jsonp",
           jsonp: "callback",
           success: function (data) {
               console.log(data)
           }
       })
   })
</script>

</body>
</html>

3.原生ajax无法实现

​ 原生Ajax受到浏览器的同源策略的限制,不允许跨域通信。

- 三.cors跨域资源共享

CORS(Cross-Origin Resource Sharing)跨源资源共享:服务器在响应头中告知浏览器受到限制或者接受哪些域的请求。简而言之,就是需要后端开放接口,允许当前的资源和其它域共享,以此跨域获取数据。

因为是需要后端设置相关的信息,Access-Control-Allow-Origin, 可跨资源访问控制的域。

header(“Access-Control-Allow-Origin:*”); // 允许任何来源 header(“Access-Control-Allow-Origin:http://localhost:8888/”); //只允许来自域名http://localhost:8888/的请求

1.准备工具

1.warmpserver集成服务环境

2.Webstorm或者VS code等编译器(也不强制要求)

2.如何使用warmpserver

wampserver下载安装使用教程_空心人的博客-CSDN博客_wamp

在www目录下建立如下的文件ajaxdemo/getapi.php, 然后在getapi.php文件中添加响应的json数据。

<?php 

   header('Access-Control-Allow-Origin: *');      ## 允许所有的域进行跨资源共享当前数据

   echo '{ "username" : "zhangsan", "content":"这是一个测试"}';     # json信息
 

创建完以上文件后,打开wampserver软件 右下角出现这个图标就说明开启服务器成功。

image-20211115002423959.png 输入http://localhost/ajaxdemo/getapi.php,php文件 访问成功

image-20211115003433553.png http://localhost:80/ajaxdemo/getapi.php

是否感觉不存在跨域?其实回顾上文可知道,http是默认80端口,而端口不同的也会存在跨域问题。

3.访问后端

因为后端设置了跨域资源共享 ,所以当浏览器发送ajax请求的时候,就不会因同源策略的存在而受到限制。

image-20211115005050152.png

4.实现代码

<!--cors后端跨域资源共享-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>client1</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是cors后端跨域资源共享</h1>
<button id="myBtn">向酷*发起请求</button>

<script>
  /*
  * 原生ajax的创建过程
  * 1.创建XMLHttpRequest对象
  * 2.设置请求成功时的回调函数
  * 3.打开url链接,开始发送请求.
  * (如果是post,中间还要设置请求头setRequestHeader("content-Type","application/x-www-form-urlencoded"))
  * 并且内容也是放在send的参数里,而get请求的参数为null)。
  * */
   window.onload = ()=>{
       let xmlhttp = null;           // 创建一个全局的xml变量
       document.querySelector("#myBtn").onclick = loadXmlDoc;
       function loadXmlDoc(){
           // 1.创建对象
           if(window.XMLHttpRequest){            // IE7 , FireFox,  Chrome, Opera, Safari
               xmlhttp = new XMLHttpRequest();
           }else{                                // IE5 IE6
               xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
           }
           // 2.设置请求成功的回调函数
           // xmlhttp.onReadyStateChange = responseData;
           // 拆分写会导致this指针指向windows,而不是XMLHttpRequest对象,也不要使用箭头函数,因为箭头函数没有自己的this指针。
           xmlhttp.onreadystatechange = function(){
               if(this.readyState === 4 && this.status === 200){
                   console.log(this.responseText);
               }
           };

           // 3.打开url链接,开始发送请求, get请求
           xmlhttp.open('GET','http://localhost/ajaxdemo/getapi.php',true);
           xmlhttp.send(null);
       }

   };
    //使用jquery发起ajax请求
    $("#myBtn").click(function () {
        $.ajax({
            url: 'http://localhost/ajaxdemo/getapi.php',
            dataType: "json",
            success: function (data) {
                console.log(data)
            }
        })
    })
</script>

</body>
</html>

- 四.使用nginx代理请求(php代理)

1.实现原理

因为ajax受到同源策略的影响,所以访问不到数据。但是如果通过nginx访问其它的网页,就不会受到同源限制。因此,nginx相当于中间接口,向目标网页发起请求,同时又返回数据给其它页面。

如果当前网页和php是在同一个域下,就不用跨域访问php文件。所以在不同的域下,还需要在php文件中设置跨域资源共享。header("Access-Control-Allow-Origin:*");

复制一张其它大佬的图

image-20211115111633020.png

2.nginx访问

后端php文件

<?php

   header('Access-Control-Allow-Origin: *');                          # 允许访问当前php文件

   #$url = 'http://www.baidu.com/';
   #$url = 'http://www.weather.com.cn/data/sk/101280101.html';       # 这个网页会返回一个json数据
   $url = 'http://searchrecommend.kugou.com/get/complex?word=love&_=1636893306422';
                                                                      # 需要使用http, 如果是https,则去除s

   echo file_get_contents($url);         #  向其它域发请请求,获取内容后并返回数据

image-20211115083615113.png

image-20211115085544662.png nginx代理访问成功

前端页面,可以通过ajax或者axios向nginx代理服务器发起请求,而nginx代理服务器就可以向其它网页发起请求。因此,达到了跨域效果。

image-20211115090833050.png

3.实现代码

<!--cors后端跨域资源共享-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>client1</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是nginx跨域</h1>
<button id="myBtn">向酷*发起请求</button>

<script>
  /*
  * 原生ajax的创建过程
  * 1.创建XMLHttpRequest对象
  * 2.设置请求成功时的回调函数
  * 3.打开url链接,开始发送请求.
  * (如果是post,中间还要设置请求头setRequestHeader("content-Type","application/x-www-form-urlencoded"))
  * 并且内容也是放在send的参数里,而get请求的参数为null)。
  * */
   window.onload = ()=>{
       let xmlhttp = null;           // 创建一个全局的xml变量
       document.querySelector("#myBtn").onclick = loadXmlDoc;
       function loadXmlDoc(){
           // 1.创建对象
           if(window.XMLHttpRequest){            // IE7 , FireFox,  Chrome, Opera, Safari
               xmlhttp = new XMLHttpRequest();
           }else{                                // IE5 IE6
               xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
           }
           // 2.设置请求成功的回调函数
           // xmlhttp.onReadyStateChange = responseData;
           // 拆分写会导致this指针指向windows,而不是XMLHttpRequest对象,也不要使用箭头函数,因为箭头函数没有自己的this指针。
           xmlhttp.onreadystatechange = function(){
               if(this.readyState === 4 && this.status === 200){
                   console.log(this.responseText);
               }
           };

           // 3.打开url链接,开始发送请求, get请求
           xmlhttp.open('GET','http://localhost/ajaxdemo/getchange.php',true);
           xmlhttp.send(null);
       }

   };
    //使用jquery发起ajax请求
    // $("#myBtn").click(function () {
    //     $.ajax({
    //         url: 'http://localhost/ajaxdemo/getchange.php',
    //         dataType: "json",
    //         success: function (data) {
    //             console.log(data)
    //         }
    //     })
    // })
</script>

</body>
</html>

其实:nginx也可以配置反向代理, 在服务器配置中设置nginx,

 location /api {
      proxy_pass http://localhost:8080;
}

不管什么情况,都可以在当前域获取到nginx中的静态资源和接口。有点类似于cors跨域资源共享。

不懂的可以看看其它文章

ajax----跨域请求sullbabe的博客-CSDN博客ajax跨域请求

- 五.使用Node正向代理

1.实现原理

让接口和当前端点同域,就可以实现node代理。但深入发现,这其实与node环境有关。比如说使用vue框架建立项目,总会开启一个开发者服务器,而当前的环境就是node环境,有node环境的支持,在配置页(vue.config.js)配置代理请求。就可以进行代理操作,以达到跨域. 但也只能在开发阶段使用, 项目上线并不会有任何效果.

与nginx代理请求的区别

  • nginx代理请求(php代理),是需要一个单独的服务器,或者同域下的服务器进行代理获取,而nginx是相当于一个中间转换站。

  • node正向代理,是使用当前域下的服务器,直接声明proxy代理,在运行的时候,会根据代理参数去访问目标网页。项目打包后代理无效.

vue:详解vue中的代理proxy_飞翔的哗哗鸡的博客-CSDN博客

image-20211115132415526.png

2.实现代码 :

在项目下新建vue.config.js,添加代理.

**注意:**当前的node正向代理是在开发者模式下, 属于构建工具上的配置. 如果说使用npm run serve打包的话,当前的正向代理是无效的. 它是webpack的配置. 但对于开发阶段需要实现跨域, 这个方法还是可行的.

module.exports = {
  devServer:{
    proxy:{
      "/api":{
        target:'http://localhost:80/',
        ws: false,        //如果要代理 websockets,配置这个参数
        secure: false,  // 如果是https接口,需要配置这个参数
        changeOrigin: true,  //是否跨域
        pathRewrite:{
          '^/api': '/'              // 生写路径
        }
      },
      "/kg":{              // 不要取api2, 因为会被上一个匹配到
        target:'https://searchrecommend.kugou.com/',
        ws: false,        
        secure: false,  
        changeOrigin: true,  
        pathRewrite:{
          '^/kg': '/'
        }
      }
    }
  },
  lintOnSave:false            // 取消lint的检测机制
}

APP.vue配置

<template>

  <h1>我是node正向代理跨域</h1>
  <button id="myBtn" @click="sendRequest">向酷*发起请求</button>
</template>

<script>

  import axios from 'axios';
  export default {

      name:'App',
       methods:{
           sendRequest(){
             axios.get("/kg/get/complex?word=love&_=1636893306422").then(res=>{
               console.log("我想直接获取");
               console.log(res);
             })
           }
       },
       mounted(){
         axios.get("/api/ajaxdemo/getchange.php").then(res=>{
           console.log("我是通过了nginx的代理(php代理)")
           console.log(res);
         })
       }
  }
</script>

当然,还有其它的方案, 不同的框架与生产环境, 使用代码也将不同. 以上是常用的方案和代码. 其它的跨域方法可以查看以下的方法.

10种跨域解决方案(终极大招)

- 总结

​ 常用的方法也就这几种:

  • jsonp实现跨域, 使用script脚本的无同源限制,只能使用get请求。
  • jquery的ajax实现跨域 ,是因为它封装了jsonp的请求结果。它是jquery独特的封装结果。
  • cors跨域资源共享,后端控制允许接受请求的域
  • nginx代理访问(php代理),服务器之间不存在跨域,nginx代理帮助我们访问。同时,nginx可以使用cors跨域资源共享,共享数据。
  • 使用Node正向代理, 开发者服务器下,配置proxy代理可以实现跨域

以上是常用的5种方法, 其实也可以归类为4种, 第二种的原理其实就和第一种类似, 实现的方法不一样而已.

到底哪一种方法更好呢? 没有什么具体的标准答案. 只有在不同的情况下, 合理地选择方案, 那将是你最优的选择.

所有代码 ShareCode:实现跨域 - Gitee.com