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

2021-10-16  本文已影响0人  前端小白的摸爬滚打

什么是跨域

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

同源

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

同源策略的限制

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

  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 方法来发送一个预检请求,根据预检请求的结果判断是否允许发送跨域请求

简单请求

需要满足下面的条件

复杂请求

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

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

postMessage

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

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

语法

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

接下来我们看个例子: 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 对象的关键属性

配置代理

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 跨域

实现

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>

总结

上一篇下一篇

猜你喜欢

热点阅读