美文网首页
JavaScript之跨域

JavaScript之跨域

作者: 庄海鑫 | 来源:发表于2018-04-02 10:30 被阅读0次

    同源策略(Same origin Policy)

    是什么?

    浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

    同源是什么?

    如果协议,端口(如果指定了一个)和域名对于两个页面是相同的,则两个页面具有相同的源。

    image.png
    需要注意的是: 对于当前页面来说页面存放的 JS 文件的域不重要,重要的是加载该 JS 页面所在什么域

    报错范例

    当我们手写一个服务器,并启动,代码、显示结果如下。。。
    server.js

    var http = require('http')
    var fs = require('fs')
    var path = require('path')
    var url = require('url')
    
    http.createServer(function(req, res){
    
      var pathObj = url.parse(req.url, true)
    
      switch (pathObj.pathname) {
        case '/getWeather':
          console.log("get weather");
          res.end(JSON.stringify({beijing: 'sunny'}))
          break;
    
        default:
          fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
            if(e){
              res.writeHead(404, 'not found')
              res.end('<h1>404 Not Found</h1>')
            }else{
              res.end(data)
            }
          })
      }
    }).listen(8011)
    

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>饥人谷</h1>
    <script>
      var xhr = new XMLHttpRequest()
      xhr.open('GET','http://localhost:8011/getWeather', true)
      xhr.send()
      xhr.onload = function(){
        console.log(xhr.responseText)
      }
    </script>
    </body>
    </html>
    
    image.png

    当我们修改index.html 中

         xhr.open('GET','http://localhost:8011/getWeather', true)
    #   xhr.open('GET','http://127.0.0.1:8011/getWeather', true)
    
    image.png

    因为不是同源,所以会报错。
    那我们的请求有没有发出去?答案是有的,看我们上面的代码,我们写了console.log("get weather")说明请求发出去了,所以终端会打印get weather


    image.png

    那我们请求发出去,有没有响应呢?别急继续往下看

    image.png
    也响应了,但是浏览器不接收,因为浏览器认为这个是不安全的,所以同源策略是浏览器的安全机制。
    总之,同源策略就是当页面向接口发请求的时候,页面的URL和接口的URL是同源的。

    JSONP

    如果我们的页面向接口请求的话,假设是不同源的,我们该怎么做?
    举个例子,我们手下有两个网站,A网站和B网站,A网站想使用B网站的某个接口获取数据,即使这两个网站在同一个服务器上,浏览器也是会阻止掉的,所以我们引用了JSONP。

    是什么?

    JSONP是通过 script 标签加载数据的方式去获取数据当做 JS 代码来执行 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现。

    JSONP举例

    server.js

    
    var http = require('http')
    var fs = require('fs')
    var path = require('path')
    var url = require('url')
    
    http.createServer(function(req, res){
      var pathObj = url.parse(req.url, true)
    
      switch (pathObj.pathname) {
        case '/getNews':
          var news = [
            "第11日前瞻:中国冲击4金 博尔特再战200米羽球",
            "正直播柴飚/洪炜出战 男双力争会师决赛",
            "女排将死磕巴西!郎平安排男陪练模仿对方核心"
          ]
          /*防止中文乱码*/
          res.setHeader('Content-Type','text/json; charset=utf-8')
          /*下面是后端为jsonp实现要做的事情*/
          if(pathObj.query.callback){
            res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')')
          }else{
            res.end(JSON.stringify(news))
          }
    
          break;
    
        default:
          fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
            if(e){
              res.writeHead(404, 'not found')
              res.end('<h1>404 Not Found</h1>')
            }else{
              res.end(data)
            }
          })
      }
    }).listen(8088)
    

    index,html

    <!DOCTYPE html>
    <html>
    <body>
    <div class="container">
      <ul class="news">
      </ul>
      <button class="show">show news</button>
    </div>
    
    <script>
    
      $('.show').addEventListener('click', function(){
        var script = document.createElement('script');
        script.src = 'http://127.0.0.1:8088/getNews?callback=appendHtml';
        document.head.appendChild(script);
        document.head.removeChild(script);
      })
    
      function appendHtml(news){
        var html = '';
        for( var i=0; i<news.length; i++){
          html += '<li>' + news[i] + '</li>';
        }
        console.log(html);
        $('.news').innerHTML = html;
      }
    
      function $(id){
        return document.querySelector(id);
      }
    </script>
    </body>
    </html>
    

    注意上面的代码index.html的script.src要和server,js监听端口号一致,否则会出现下面问题

    image.png

    正常应该是这样显示

    image.png

    注意当前页面是localhost:8088/index.html,而我们请求的是127.0.0.1:8088/index.html
    JSONP是比较HACK的跨域方法,现在不常使用


    CORS(Cross-Origin Resource Sharing)

    是什么?

    CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。 实现方式很简单,当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。

    CORS举例

    server.js

    
    var http = require('http')
    var fs = require('fs')
    var path = require('path')
    var url = require('url')
    
    http.createServer(function(req, res){
     var pathObj = url.parse(req.url, true)
    
     switch (pathObj.pathname) {
       case '/getNews':
         var news = [
           "第11日前瞻:中国冲击4金 博尔特再战200米羽球",
           "正直播柴飚/洪炜出战 男双力争会师决赛",
           "女排将死磕巴西!郎平安排男陪练模仿对方核心"
           ]
    
         res.setHeader('Access-Control-Allow-Origin','http://localhost:8081')
         //res.setHeader('Access-Control-Allow-Origin','*')
         res.end(JSON.stringify(news))
         break;
       default:
         fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
           if(e){
             res.writeHead(404, 'not found')
             res.end('<h1>404 Not Found</h1>')
           }else{
             res.end(data)
           }
         }) 
     }
    }).listen(8081)
    

    index.html

    <!DOCTYPE html>
    <html>
    <body>
      <div class="container">
        <ul class="news">
    
        </ul>
        <button class="show">show news</button>
      </div>
    
    <script>
    
      $('.show').addEventListener('click', function(){
        var xhr = new XMLHttpRequest()
        xhr.open('GET', 'http://127.0.0.1:8081/getNews', true)
        xhr.send()
        xhr.onload = function(){
          appendHtml(JSON.parse(xhr.responseText))
        }
      })
    
      function appendHtml(news){
        var html = ''
        for( var i=0; i<news.length; i++){
          html += '<li>' + news[i] + '</li>'
        }
        $('.news').innerHTML = html
      }
    
      function $(selector){
        return document.querySelector(selector)
      }
    </script>
    
    
    
    </html>
    

    第一种是正常情况


    image.png

    第二种就是跨域


    image.png
    加了一个声明,Access-Control-Allow-Origin,允许哪些域使用我的数据。

    JSONP和CORS本质是从不同域的接口获取数据,下面我们要说一下如何iframe跨域。

    降域

    比如当前页面下,我们埋了一个iframe,iframe对应的地址是taobao.com,假设你刚刚用电脑访问过淘宝,买了一些东西,然后又访问这个页面,而这个iframe会自动加载,并且是登录状态。假设浏览器没有做一些安全限制,那在当前页面下,我们是可以使用JavaScript来操控iframe上的内容,我们就可以知道淘宝用户是谁,当前金额是多少,我们甚至可以操作模拟用户的点击,所以浏览器不会让你这么做,所以浏览器想到一个办法,只有是当前域名的iframe,你才可以访问里面的东西。换句话说,我们的页面埋下的iframe页面,如果是不同域,虽然可以加载,但是外部的JavaScript是无法操作里的内容。

    举例

    a.html

    <html>
    
    <head>
    
    <meta charset="utf-8">
    
    <style>
    
    .ct{
    
    width: 910px;
    
    margin: auto;
    
    }
    
    .main{
    
    float: left;
    
    width: 450px;
    
    height: 300px;
    
    border: 1px solid #ccc;
    
    }
    
    .main input{
    
    margin: 20px;
    
    width: 200px;
    
    }
    
    .iframe{
    
    float: right;
    
    }
    
    iframe{
    
    width: 450px;
    
    height: 300px;
    
    border: 1px dashed #ccc;
    
    }
    
    </style>
    
    </head>
    
    <div class="ct">
    
    <h1>使用降域实现跨域</h1>
    
    <div class="main">
    
    <input type="text" placeholder="http://a.com:8080/a.html">
    
    </div>
    
    <iframe src="http://b.com:8080/b.html" frameborder="0" ></iframe>
    
    </div>
    
    <script>
    
    //URL: http://a.jrg.com:8080/a.html
    
    document.querySelector('.main input').addEventListener('input', function(){
    
    console.log(this.value);
    
    window.frames[0].document.querySelector('input').value = this.value;
    
    })
    
    document.domain = "jrg.com"
    
    </script>
    
    </html>
    

    b.html

    
    <html>
    <head>
      <meta charset="utf-8">
    <style>
        html,body{
            margin: 0;
        }
        input{
            margin: 20px;
            width: 200px;
        }
    </style>
    </head>
    <body>
    <input id="input" type="text"  placeholder="http://b.jrg.com:8080/b.html">
    <script>
    
    // URL: http://b.jrg.com:8080/b.html
     
    document.querySelector('#input').addEventListener('input', function(){
        window.parent.document.querySelector('input').value = this.value;
    })
    
    document.domain = 'jrg.com';
    
    </script>
    </body>
    </html>
    
    image.png

    上图分为左右框,右框为iframe页面,我们这时操作iframe,出现报错
    因为当前页面和ifame页面是不同域的。
    当我们修改使其为同一域

    image.png

    假使当前页面a.jrg.com ,而iframe为b.jrg.com ,这时我们使用document.domain='jrg.com' 这样就可以操作iframe了,这就是通过降域实现操作iframe


    postMessage

    假设现在我们有一个页面,我写的页面是在我的域名下。我希望我这个页面可以供其他人使用。其他人可以把我的页面作为iframe页面嵌入到自己的页面当中去。我还希望他们可以在他们的页面上操作我的页面,因为我愿意提供自己的页面,可是按照同源机制,只要域名不一样,他们就不可以操作我的页面,除非我们的主域是相同的,这时该怎么办呢?需要用到postmessage

    是什么?

    postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
    postMessage(data,origin)方法接受两个参数
    1.data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
    2.origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

    比如说我们的网站嵌入一个官网叫mychat,我希望我的页面和它里面的内容进行交互,mychat就可以支持postmessage,我就可以向他post我的消息,它收到之后,做对应的事情,它给我一个文档告诉我怎么做。

    举例

    a.html

    <html>
    
    <head>
    
    <meta charset="utf-8">
    
    <style>
    
    .ct{
    
    width: 910px;
    
    margin: auto;
    
    }
    
    .main{
    
    float: left;
    
    width: 450px;
    
    height: 300px;
    
    border: 1px solid #ccc;
    
    }
    
    .main input{
    
    margin: 20px;
    
    width: 200px;
    
    }
    
    .iframe{
    
    float: right;
    
    }
    
    iframe{
    
    width: 450px;
    
    height: 300px;
    
    border: 1px dashed #ccc;
    
    }
    
    </style>
    
    </head>
    
    <body>
    
    <div class="ct">
    
    <h1>使用postMessage实现跨域</h1>
    
    <div class="main">
    
    <input type="text" placeholder="http://a.jrg.com:8080/a.html">
    
    </div>
    
    <iframe src="http://localhost:8080/b.html" frameborder="0" ></iframe>
    
    </div>
    
    <script>
    
    //URL: http://a.jrg.com:8080/a.html
    
    $('.main input').addEventListener('input', function(){
    
    console.log(this.value);
    
    window.frames[0].postMessage(this.value,'*');
    
    })
    
    window.addEventListener('message',function(e) {
    
    $('.main input').value = e.data
    
    console.log(e.data);
    
    });
    
    function $(id){
    
    return document.querySelector(id);
    
    }
    
    </body>
    
    </script>
    
    </html>
    

    b.html

    
    <html>
    <head>
        <meta charset="utf-8">
        <style>
            html,body{
                margin: 0;
            }
            input{
                margin: 20px;
                width: 200px;
            }
        </style>
    </head>
    <body>
        <input id="input" type="text"  placeholder="http://b.jrg.com:8080/b.html">
        <script>
    
        // URL: http://b.jrg.com:8080/b.html
         
        $('#input').addEventListener('input', function(){
            window.parent.postMessage(this.value, '*');
        })
    
        window.addEventListener('message',function(e) {
                $('#input').value = e.data
            console.log(e.data);
        });
    
        function $(id){
            return document.querySelector(id);
        }   
    
        </script>   
    </body>
    
    
    </html>
    

    参考资料:同源策略
    postmessage

    相关文章

      网友评论

          本文标题:JavaScript之跨域

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