为了账号安全,请及时绑定邮箱和手机立即绑定

跨域

标签:
Html/CSS

同源策略


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

解释同源之前先对url进行解析

webp


同源指的是:

  • 协议相同

  • 域名相同

  • 端口相同

只要不满足上述3个条件的任意一个,即为不同源,也就是我们常说的跨域
同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据

不同源的几种情况:

  • 协议不同

http://www.a.com/a.jshttps://www.a.com/a.js
  • 域名不同

http://www.a.com/a.jshttp://www.b.com/a.js
  • 端口不同

http://www.a.com:80/a.jshttp://www.a.com:8080/a.js

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

如果不同源,共有3种行为受到限制

  1. 无法读取非同源网页的Cookie、LocalStorage、IndexedDB

  2. 无法接触非同源网页的DOM

  3. 无法向非同源地址发送Ajax请求(可以发送,但浏览器会拒绝接受响应)

跨域的几种实现方式


当网站A需要获取网站B的数据就涉及到跨域了,本文讲述的跨域方式有以下几种:

1. JSONP(JSON with Padding)

HTML中的script标签可以加载其他域下的js,利用这点我们可以在网页上通过添加一个script元素,向服务器请求JSON数据,这种做法不受同源策略的限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来

<body>
    <div class="container">
        <ul class="news"></ul>
        <button class="show">show news</button>
    </div>

    <script>
        var ul = document.querySelector('.news');        var btn = document.querySelector('.show');

        btn.addEventListener('click', function(){            var script = document.createElement('script');
            script.setAttribute('src', 'http://127.0.0.1:8080/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>';
            }
            ul.innerHTML = html;
        }    </script></body>
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 = [                '苹果',                '香蕉',                '猕猴桃'
            ];
            res.setHeader('Content-type', 'text/json; charset=utf-8');            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(8080);

使用node.js搭建服务器,响应数据如下所示:

webp


点击按钮时,页面呈现效果如下所示:


webp

JSONP的最大特点是老式浏览器全部支持,兼容性好,服务器的改造非常小;但缺点是只支持get请求,且错误处理机制不如XMLHttpRequest

2. CORS(Cross-Origin Resource Sharing)

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

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的ajax通信没有差别,代码一样。浏览器一旦发现ajax请求跨源,就会自动添加一些附加的请求头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信

<body>
    <div class="container">
        <ul class="news"></ul>
        <button class="show">show news</button>
    </div>

    <script>
        var ul = document.querySelector('.news');        var btn = document.querySelector('.show');

        btn.addEventListener('click', function(){            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'http://127.0.0.1:8080/getNews', true);
            xhr.send();
            xhr. = function(){
                appendHtml(JSON.parse(xhr.responseText))
            }
        })        function appendHtml(news){            var html = '';            for(var i = 0; i < news.length; i++){
                html += '<li>' + news[i] + '</li>';
            }
            ul.innerHTML = html;
        }    </script></body>
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 = [                '苹果',                '香蕉',                '猕猴桃'
            ];
            res.setHeader('Content-type', 'text/json; charset=utf-8');
            res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
            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(8080);

使用node.js搭建服务器,请求如下所示:

webp


若将Access-Control-Allow-Origin改为*,则包含所有,任何网站都可以访问


3. 降域


例如:A网站的a.html访问B网站的b.htmlA网站的域名a.xxx.com:8080B网站的域名b.xxx.com:8080a.html上嵌入iframeb.html。在同源策略下这两者之间域名不同是不能互相访问的,只有在同源的情况下,父窗口和子窗口才能通信;如果跨域,就无法拿到对方的DOM。为达到目的,我们可以采取降域措施,将两者的域名设置为document.domain = 'xxx.com',完整的代码如下所示
a.html

<body>    <div class="ct">
        <h1>使用降域实现跨域</h1>
        <div class="main">
            <input type="text" placeholder="http://a.xxx.com:8080/a.html">
        </div>
        <iframe class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://b.xxx.com:8080/b.html" frameborder="0"></iframe>
    </div>

    <script>
        var input = document.querySelector('.main input');
        input.addEventListener('input', function(){            console.log(this.value);            window.frames[0].document.querySelector('input').value = this.value;
        });        document.domain = 'xxx.com';    </script></body>

b.html

<body>    <input id="input" type="text" placeholder="http://b.xxx.com:8080/b.html">

    <script>
        var input = document.querySelector('#input');
        input.addEventListener('input', function(){            window.parent.document.querySelector('input').value = this.value;
        });        document.domain = 'xxx.com';    </script></body>

为模拟两个不同域名下的页面互相访问,我们可在本地修改host,修改如下:

127.0.0.1  a.xxx.com192.168.1.23  b.xxx.com

因此在开始http-server后,a.htmlb.html分别对应:

127.0.0.1:8080/a.html  a.xxx.com:8080/a.html
192.168.1.23:8080/b.html  b.xxx.com:8080/b.html

当在父窗口的输入框中输入字符时,子窗口的输入框也会输入相同的内容,效果如图所示:


webp

注:此方法只适用于一级域名相同,只是二级域名不同的两个窗口,把么就可设置document.domain属性,规避同源策略

4. postMessage


仍以上个例子举例,父窗口a.xxx.com向子窗口b.xxx.com发消息,在不改变domain的情况下,调用postMessage方法即可

H5引入新的APIwindow.postMeaasge,此方法提供了一种受控机制来规避同源策略的限制,只要正确的使用,就可以安全地实现跨源通信,无论这两个窗口是否同源。

语法:

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

otherWindow:其他窗口的一个引用,比如iframecontentWindow、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
message:将要发送到其他window的数据
targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串*(表示无限制)或者一个URI(协议+域名+端口)
transfer(可选):是一串和message同时传递的Transferable对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权

当在父窗口使用postMessage向子窗口传递消息时,需在子窗口监听message事件

window.addEventListener('message', receiveMessage, false);function receiveMessage(event){  var origin = event.origin;  if(origin !== 'http://b.xxx.com:8080'){    return;
  }else {    console.log(event.data);
  }
}

message事件的参数是事件对象event,提供以下3个属性:

  • event.data:消息内容

  • event.origin:调用postMessage时消息发送方窗口的origin(协议+域名+端口)

  • event.source:对发送消息的窗口对象的引用

a.html

<body>    <div class="ct">
        <h1>使用postMessage跨域</h1>
        <div class="main">
            <input type="text" placeholder="http://a.xxx.com:8080/a.html">
        </div>
        <iframe class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://b.xxx.com:8080" frameborder="0"></iframe>
    </div>

    <script>
        var input = document.querySelector('.ct input');
        input.addEventListener('input', function(){            window.frames[0].postMessage(this.value, '*');            console.log(this.value)
        });        window.addEventListener('message', function(e){
            input.value = e.data;            console.log(e.data);
        });    </script></body>

b.html

<body>    <input id="input" type="text" placeholder="http://b.xxx.com:8080/b.html">

    <script>
        var input = document.querySelector('#input');
        input.addEventListener('input', function(){            window.parent.postMessage(this.value, '*');            console.log(this.value)
        });        
        window.addEventListener('message', function(e){
            input.value = e.data;            console.log(e.data);            console.log(e.source);            console.log(e.origin); 
        });    </script></body>

当在父窗口的输入框中输入字符时,子窗口的输入框也输入相同的内容,如图所示:


webp

打印如下:


webp



作者:饥人谷_Tracy
链接:https://www.jianshu.com/p/7e1031549599


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消