美文网首页
跨域 & 跨域的几种解决方式

跨域 & 跨域的几种解决方式

作者: 前端小白的摸爬滚打 | 来源:发表于2021-10-16 17:21 被阅读0次

    什么是跨域

    跨域是由于浏览器同源策略的限制,它是对 JavaScript 的限制。浏览器不允许执行其他网站的脚本

    同源

    协议、域名、端口号需要相同

    同源策略的限制

    • DOM 无法获得

    • Cookie、WebStorage、IndexDB 无法读取

    • ajax 请求无法发送

    但是有三个标签可以加载跨域的资源

    1. link

    2. script

    3. img

    需要注意的是:1. 由协议/端口号导致的跨域,前端是无能为力的; 2. 跨域仅仅比较的是 url,不会比较 ip 地址是否相同

    跨域的请求是可以发送出去的,而且服务器也是可以正常的接受&处理&返回响应内容,只不过服务器返回的结果被浏览器拦截了
    表单是可以发送跨域请求的,但是为什么 ajax 就不可以?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

    跨域的解决方式

    JSONP

    原理

    利用的就是 script 加载的资源不会受到同源策略的限制,所以我们可以将我们要请求数据的 url 赋值给 script 的 src 属性,并且该 url 需要携带一个参数,例如 url?callback=cb。相应的服务器也需要支持这个请求,处理请求之后,将请求返回的结果作为参数传递给 cb 函数 ,然后将cb(相应内容)返回给浏览器。浏览器定义了这个函数,函数接受的参数就是请求返回的数据。

    封装一个 jsonp 函数

    function jsonp(url, params, callback) {
      return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        const arr = [];
        for (let key in params) {
          arr.push(`${key} = ${params[key]}`)
        }
        arr.push(`callback=${callback}`)
        script.src = url + '?' + arr.join('&');
        document.body.append(script);
        window[callback] = function (data) {
          resolve(data);
          document.body.removeChild(script)
        }
      })
    }
    

    优缺点

    优点:简单、兼容性好

    缺点:只能发送 get 请求

    CORS

    跨源资源共享,是跨域的根本解决方法,需要服务器的支持

    浏览器在跨域的时候会自动发送一个 CORS 的请求的,所以最关键的就是需要服务器的支持

    服务器设置 Access-Control-*相关的头部用于支持 CORS 请求

    在通过 CORS 来解决跨域问题的时候会涉及到两个概念:简单请求和复杂请求。对于复杂请求,在真正的请求被发送之前,浏览器会先使用 OPTIONS 方法来发送一个预检请求,根据预检请求的结果判断是否允许发送跨域请求

    简单请求

    需要满足下面的条件

    • 请求方法:GET、POST、HEAD

    • 没有自定义的请求头

    • Content-Type 的值为: text/plain、multipart/form-data、application/x-www-form-urlencoded

    • 没有监听 XMLHttpRequestUpload(XMLHttpRequest.upload)对象的事件

    复杂请求

    不满足上述简单请求的条件的请求

    对于复杂请求,在真正的请求被发送之前,浏览器会先使用 OPTIONS 方法来发送一个预检请求,根据预检请求的结果判断是否允许发送跨域请求

    postMessage

    是为数不多的可以跨域操作的 window 属性之一

    postMessage 方法允许来自不同源的脚本采用异步的方式进行有限的通信,可以实现跨文档、多窗口、跨域消息的传递

    语法

    otherWindow.postMessage(message, origin, [transfer])

    • otherWindow: 其他 window 的引用。比如说:iframe.contentWindow、window.open 返回的对象

    • message:发送的数据

    • origin:数据发送到的域名,可以为*,表示任意域名都可以接收到发送的数据

    • transfer(可选):是一串和 message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

    接下来我们看个例子: http://localhost:3000/a.html 页面向 http://localhost:4000/b.html 传递“我爱你”,然后后者传回"我不爱你"。

    // a.html
      <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
      //内嵌在http://localhost:3000/a.html
        <script>
          function load() {
            let frame = document.getElementById('frame')
            // frame.contentWindow 代表的是iframe的window对象
            // 你可以使用这个 Window 对象去访问这个iframe的文档和它内部的DOM. 这个是可读属性, 但是它的属性像全局Window 一样是可以操作的.
            // 所以postMessage的target Origin其实是和调用该方法的窗口所加载的域名相同
            frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
            window.onmessage = function(e) { //接受返回数据
              console.log(e.data) //我不爱你
            }
          }
        </script>
    
    // b.html
      window.onmessage = function(e) {
        console.log(e.data) //我爱你
        // e.source是a.html的window对象
        e.source.postMessage('我不爱你', e.origin)
     }
    

    message 事件的 event 对象的关键属性

    • data: 接收到的数据

    • source: 发送数据的窗口的引用

    • origin:发送数据的域名

    配置代理

    Charles、Nginx

    window.name + iframe

    window.name 的独特之处在于:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。同一个窗口中,加载的所有页面无论是否是跨域都可以共享 window.name

    通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

    为什么要将 iframe 的 src 转换为加载本地文件才可以访问其设置的 window.name,原因是:因为规定如果 HTML 页面和和该页面里的 iframe 框架的 src 如果不同源,则也无法操作框架里的任何东西,所以就取不到 iframe 框架的 name 值了

    location.hash + ifame

    原理

    a.html 欲与 c.html 跨域相互通信,通过中间页 b.html 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。

    a 和 b 同域

    a 和 c 跨域

    实现

    • a.html 页面中通过使用 iframe 加载一个 src 为 c.html 的页面,并且给其传递一个 hash 值(数据)

    • c.html 页面通过 location.hash 可以获取到 a.html 传递给他的数据

    • c.html 页面创建一个 iframe 加载一个和 a.html 同域名的 b.html 并且将它要传递给 a.html 的数据最为 iframe 的 hash 值传递给 b.html

    • b.html 设置 window.parent(c.html).parent(a.html).location.hash = location.hash(c 要传递给 a 的数据) 来将 c 传递的数据写入 a 的 hash 中(因为 a、b 是不跨域的,所以可以操作 js)

    document.domain + iframe

    该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 就可以使用这种方式来实现跨域

    只需要给页面添加 document.domain = 'test.com'就可以实现二级域名相同的与可以跨域

    实现原理

    两个页面都通过 document.domain 来强制设置了基础主域,从而实现了同域。

    我们看个例子:页面 a.zf1.cn:3000/a.html 获取页面 b.zf1.cn:3000/b.html 中 a 的值

    // a.html
    <body>
     helloa
      <iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
      <script>
        document.domain = 'zf1.cn'
        function load() {
          console.log(frame.contentWindow.a);
        }
      </script>
    </body>
    
    // b.html
    <body>
       hellob
       <script>
         document.domain = 'zf1.cn'
         var a = 100;
       </script>
    </body>
    

    总结

    • CORS 支持所有类型的 http 请求,是跨域 http 请求的根本解决方案

    • JSONP 只支持 GET 请求,JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。

    相关文章

      网友评论

          本文标题:跨域 & 跨域的几种解决方式

          本文链接:https://www.haomeiwen.com/subject/zauboltx.html