js跨域

作者: Super曲江龙Kimi | 来源:发表于2019-07-26 12:37 被阅读0次

js跨域

1.同源策略

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制内容有:
Cookie、LocalStorage、IndexedDB 等存储性内容
DOM 节点
AJAX 请求发送后,结果被浏览器拦截了

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

2.常见跨域场景

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。常见跨域场景如下图所示:

1.png

3. 实现跨域的九种方式

jsonp

有几个标签是允许跨域加载资源:

<img src=XXX>
<link href=XXX>
<script src=XXX>
<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链

JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击(jsonp请求返回<script></script>)。

   <script>
      function kimi(data) {
        console.log(data)
      }
    </script>
    <script src="https://www.baidu.com/sugrec?prod=pc&wd=8&cb=kimi">
    </script>

cors

浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。

服务端设置了Access-Control-Allow-Origin就开启了CORS通信,表示允许哪个域来访问我。
具体需要设置的头部有很多,列举常用的:

    // 设置哪个源可以访问我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 允许携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // 允许哪个方法访问我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age', 6)
    // 允许返回的头
    res.setHeader('Access-Control-Expose-Headers', 'name')

postMessage

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递
  • 上面三个场景的跨域数据传递
// a.html
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe>
  
    <script>
      function load() {
        let frame = document.getElementById('frame')
        frame.contentWindow.postMessage('kimi', 'http://localhost:4000') //发送数据
        window.onmessage = function(e) { //接受返回数据
          console.log(e.data) //yes
        }
      }
    </script>
// b.html
  window.onmessage = function(e) {
    console.log(e.data) //kimi
    e.source.postMessage('yes, e.origin)
 }

http-proxy

一般在使用webpack时webpack-dev-server会加proxy-table来转发请求。因为同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略

nginx

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。

//  nginx proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;
    }
}

websocket

websocket 是没有跨域限制的。

// socket.html
<script>
    let socket = new WebSocket('ws://localhost:3000');
    socket.onopen = function () {
      socket.send('kimi1');//向服务器发送数据
    }
    socket.onmessage = function (e) {
      console.log(e.data);//接收服务器返回的数据
    }
</script>
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
  ws.on('message', function (data) {
    console.log(data);
    ws.send('kimi2')
  });
})

document.domain

该方式只能用于二级域名相同的情况下,比如 video.baidu.comwww.baidu.com 适用于该方式。

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

window.name

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

实现:先在a页面中嵌入c页面,在c页面中写入window.name值, c加载完毕后马上切换成a同源的b页面。等b加载完后可以拿到没有清除的name值

// a.html(http://localhost:3000/b.html)
  <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
  <script>
    let first = true
    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    function load() {
      if(first){
      // 第1次onload(跨域页)成功后,切换到同域代理页面
        let iframe = document.getElementById('iframe');
        iframe.src = 'http://localhost:3000/b.html';
        first = false;
      }else{
      // 第2次onload(同域b.html页)成功后,读取同域window.name中数据
        console.log(iframe.contentWindow.name);
      }
    }
  </script>
 // c.html(http://localhost:4000/c.html)
  <script>
    window.name = 'yes'  
  </script>

location.hash

a页面嵌入c页面带给他hash数据,c页面中拿到后,再嵌入和a同源的b页面带入hash数据,b拿到后再设置回a页面的hash中。a中再监听hash变化来获取。

// a.html
  <iframe src="http://localhost:4000/c.html#kimi"></iframe>
  <script>
    window.onhashchange = function () { //检测hash的变化
      console.log(location.hash);
    }
  </script>
 // b.html
  <script>
    window.parent.parent.location.hash = location.hash 
    //b.html将结果放到a.html的hash值中,b.html可通过parent.parent访问a.html页面
  </script>
 // c.html
 console.log(location.hash);
  let iframe = document.createElement('iframe');
  iframe.src = 'http://localhost:3000/b.html#yes';
  document.body.appendChild(iframe);

总结

现在业界主流使用解决跨域问题的方案主要是 nginx和cors、jsonp(优势在于可以兼容老版本浏览器,但是只支持get并且不安全),本地开发常用node-proxy的方式做反向代理。其他的方式都不常用。location.hash和window.name都需要有第三方中介。而domain的方式会有域名的限制条件。

相关文章

  • 跨域

    跨域 什么是跨域: 解决跨域 通过jsonp原理:在页面引入跨域js和css时,没有存在跨域问题.因此可以动态创建...

  • 跨域问题详解分析

    参考文档 CORS详解 跨域资源共享 CORS 详解 js中几种实用的跨域方法原理详解 跨域的那些事儿 跨域与跨域...

  • 2021-02-23

    一、什么是跨域 二、vue.config.js 跨域配置

  • HTML-获取iframe元素的正确方法

    跨域相关文章详解js跨域问题JavaScript跨域总结与解决办法 解释最清楚的jsonpWhat is JSON...

  • JSONP、CORS、跨域

    跨域 同源:两个文档同源需满足:协议、域名、端口相同跨域:不同域之间相互请求资源,就算作“跨域“。js进行DOM操...

  • 怎么能学好Web前端开发,如何去解决JS跨域问题

    如何去解决JS跨域问题?怎么能学好Web前端开发?JavaScript跨域是指通过JS在不同的域之间进行数据传输或...

  • 如何去解决JS跨域问题 怎么能学好Web前端开发

    如何去解决JS跨域问题?怎么能学好Web前端开发?JavaScript跨域是指通过JS在不同的域之间进行数据传输或...

  • 关于js中的跨域

    @(JS技巧)[跨域] 各种跨域方法详解 总是在听说跨域,可是自己除了JSON,其它的方法其实并不是真的理解。今晚...

  • vue 跨域的配置

    关于跨域的配置真是累呀 在网上找了资料 才成功实现跨域了 其实主要在vue.config.js 来配置跨域

  • 3.跨域

    什么是跨域? js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过...

网友评论

      本文标题:js跨域

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