美文网首页我爱编程
13.跨域两三事

13.跨域两三事

作者: 笨蛋小明 | 来源:发表于2018-06-06 12:29 被阅读0次

一、什么是跨域

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。

1.1造成跨域的两种策略

浏览器的同源策略会导致跨域,这里同源策略又分为以下两种

(1)DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。

(2)XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。

只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。

所谓同源是指,域名,协议,端口均相同,不明白没关系,举个栗子:

http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)

http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)

http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)

http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)

http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)

请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。

浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。

1.2为什么要有跨域限制

跨域限制主要是为了安全考虑

AJAX同源策略主要用来防止CSRF攻击。如果没有AJAX同源策略,相当危险,我们发起的每一次HTTP请求都会带上请求地址对应的cookie,那么可以做如下攻击:

1.  用户登录了自己的银行页面 [http://mybank.com](https://www.jianshu.com/u/9f1d1dcbe846),[http://mybank.com](https://www.jianshu.com/u/9f1d1dcbe846)向用户的cookie中添加用户标识。
2.  用户浏览了恶意页面 [http://evil.com](https://www.jianshu.com/u/9f1d1dcbe846)。执行了页面中的恶意AJAX请求代码。
3.  [http://evil.com](https://www.jianshu.com/u/9f1d1dcbe846)向[http://mybank.com](https://www.jianshu.com/u/9f1d1dcbe846)发起AJAX HTTP请求,请求会默认把[http://mybank.com](https://www.jianshu.com/u/9f1d1dcbe846)对应cookie也同时发送过去。
4.  银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。
5.  而且由于Ajax在后台执行,用户无法感知这一过程。

DOM同源策略也一样,如果iframe之间可以跨域访问,可以这样攻击:

1.  做一个假网站,里面用iframe嵌套一个银行网站 [http://mybank.com](https://www.jianshu.com/u/9f1d1dcbe846)。
2.  把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
3.  这时如果用户输入账号密码,我们的主网站可以跨域访问到[http://mybank.com](https://www.jianshu.com/u/9f1d1dcbe846)的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。

所以说有了跨域跨域限制之后,我们才能更安全的上网了。

二、跨域的解决方式

2.1跨域资源共享

//url(必须),options(可选)
fetch('/some/url',{
  method: 'get',
}).then(function(reponse){
  
}).catch(function(err){
  //出错了;等价于 then 的第二个参数,但这样更好用更直观
})

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。详见我的另一篇文章CORS

cors为什么就能支持跨域通信呢?---浏览器会拦截ajax请求如果这个请求是跨域的 ,他会在http头中加 orign

2.2 JSONP

JSONP(JSON with Padding)是资料格式 JSON 的一种“使用模式”,可以让网页从别的网域要资料。

JSONP也叫填充式JSON,是应用JSON的一种新方法,只不过是被包含在函数调用中的JSON,例如:

callback({"name","getName"});

JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。

2.2.1 JSONP原理
在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。

翻译过来就是通过动态创建script标签,然后利用src属性进行跨域。

<script type="text/javascript">
    function getName(jsondata){
        //处理获得的json数据
    }
</script>
<script src="http://example.com/data.php?callback=getName"></script>

js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。

<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>

最终,输出结果为:getName(['a','b','c']);

如果你的页面使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。

<script type="text/javascript">
    $.getJSON('http://example.com/data.php?callback=?,function(jsondata)'){
        //处理获得的json数据
    });
</script>

jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

下面这段JS可能会有助更好的理解JSONP跨域原理

// 定义一个fun函数
function fun(fata) {
    console.log(data);
};
// 创建一个脚本,并且告诉后端回调函数名叫fun
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'demo.js?callback=fun';
body.appendChild(script);
返回的js脚本,直接会执行。所以就执行了事先定义好的fun函数了,并且把数据传入了进来。

fun({"name": "name"})

2.2.2 JSONP的优缺点

JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果

JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

为什么JSONP只支持GET方法?

因为基本原理就是通过动态创建script标签,然后利用src属性进行跨域。
总结起来就是一句话:因为<script>标签,只支持GET,你觉得js文件可以post吗?

2.3 服务器代理

浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。

服务器代理是万能的。

2.4 document.domain来跨子域

浏览器都有一个同源策略,其限制之一就是第一种方法中我们说的不能通过ajax的方法去请求不同源中的文档。 它的第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。

不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。比如,有一个页面,它的地址是http://www.example.com/a.html , 在这个页面里面有一个iframe,它的src是http://example.com/b.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的:

<script type="text/javascript">
    function test(){
        var iframe = document.getElementById('ifame');
        var win = document.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
        var doc = win.document;//这里获取不到iframe里的document对象
        var name = win.name;//这里同样获取不到window对象的name属性
    }
</script>
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>

这个时候,document.domain就可以派上用场了,我们只要把http://www.example.com/a.html ,和 http://example.com/b.html,这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,主域必须相同。

1.在页面 http://www.example.com/a.html 中设置document.domain:

<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'example.com';//设置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
    }
</script>

2.在页面 http://www.example.com/b.html 中也设置document.domain:

<script type="text/javascript">
    document.domain = 'example.com';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>
修改document.domain的方法只适用于不同子域的框架间的交互。

2.5 window.name

window.name 传输技术,原本是 Thomas Frank 用于解决 cookie 的一些劣势(每个域名 4 x 20 Kb的限制、数据只能是字符串、设置和获取 cookie 语法的复杂等等)而发明的。后来 Kris Zyp 在此方法的基础上强化了 window.name 传输 ,用来解决跨域数据传输问题。

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

** window.name 传输技术的基本原理:**

当在浏览器中打开一个页面,或者在页面中添加一个iframe时即会创建一个对应的window对象,当页面加载另一个新的页面时,window的name属性是不会变的。这样就可以利用在页面动态添加一个iframe然后src加载数据页面,在数据页面将需要的数据赋值给window.name。然而此时承载iframe的parent页面还是不能直接访问,不在同一域下iframe的name属性,这时只需要将iframe再加载一个与承载页面同域的空白页面,即可对window.name进行数据读取

示例:

//www.test.com下a.html页:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>

        <button>click!</button>

        <script type="text/javascript">

            var a=document.getElementsByTagName("button")[0];

            a.onclick=function(){                               //button添加click事件
                var inf=document.createElement("iframe");       //创建iframe
                inf.src="http://www.domain.com/window.name/b.html"+"?h=5"  //加载数据页www.domain.com/window.name/b.html同事携带参数h=5
                var body=document.getElementsByTagName("body")[0];
                body.appendChild(inf);                          //引入a页面

                inf.onload=function(){
                    inf.src='http://www.test.com/b.html'       //iframe加载完成,加载www.test.com域下边的空白页b.html
                    console.log(inf.contentWindow.name)        //输出window.name中的数据
                    body.removeChild(inf)                      //清除iframe
                }
            }

        </script>
    </body>
</html>
//www.domain.com下b.html页:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <a href="" target="" title="">2</a>
        <script type="text/javascript" src="../cross/jquery-2.2.3.js">

        </script>
        <script>
            var str=window.location.href.substr(-1,1);      //获取url中携带的参数值
            $.ajax({
                type:"get",
                url:"http://www.domain.com/a.php"+"?m="+str, //通过ajax将查询参数传给php页面
                async:true,
                success:function(res){
                    window.name=res                         //将接收到的查询数据赋值给window.name
                },
                error:function(){
                    window.name='error'                      //..
                }
            });
        </script>
    </body>
</html>

www.test.com 下a页面点击button,完美跨域获取到了www.domain.com 下b页面通过ajax请求从a.php得来的数据。

2.6 location.hash跨域

javascript 中 location.hash 是指 www.aa.com/a.php#abc 中的#abc

Hash的改变页面不会刷新

如何跨域

如果index页面要获取远端服务器的数据,动态插入一个iframe,将iframe的src属性指向服务端地址。这时top window和包裹这个iframe的子窗口是不能通信的(同源策略),所以改变子窗口的路径就行了,将数据当做改变后的路径的hash值加在路径上,然后就能通信了(和window.name跨域几乎相同),将数据加在index页面地址的hash值上。index页面监听地址的hash值变化(html5有hashchange事件,用setInterval不断轮询判断兼容ie6/7),然后做出判断,处理数据。

利用hash,场景是当前页面A 通过iframe或frame嵌入了跨域的页面B

//在A中伪代码如下:
var B = document.getElementsByTagName('frame');
B.src = B.src + '#' + data;
//在B中伪代码如下:
window.onhashchange = function(){
  var data = window.location.hash;
}

2.7 postmessage

//窗口A(http://A.com)向跨域的窗口B(http://B.com)发送信息
Bwindow.postMessage('data','http://B.com');
//在窗口B中监听
window.addEventListener('message',function(event){
  console.log(event.origin);// http://A.com 这样我们就可以选择接受不同域名发送过来的消息了
  console.log(event.source)// Awindow
  console.log(event.data)// data!
},false)

2.8 WebSocket

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};

关于WebSocket详见我的另一篇文章WebSocket

参考资料:
《详解js跨域问题》

相关文章

  • 13.跨域两三事

    一、什么是跨域 跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。 ...

  • 深入跨域问题(3) - 利用 JSONP 解决跨域

    深入跨域问题(1) - 初识 CORS 跨域资源共享;深入跨域问题(2) - 利用 CORS 解决跨域深入跨域问题...

  • 关于设置env等环境变量的思考

    1、如何处理跨域后台处理跨域前端处理跨域浏览器处理跨域 前端本地处理跨域:代理线上跨域的处理方式:Nginx反向代...

  • Web前后端跨域问题处理

    跨域问题有前台跨域(iframe间)和后台跨域。 前台跨域的解决方案可以采用跨域文档通讯(Cross domain...

  • 跨域

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

  • 跨域问题详解分析

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

  • 跨域问题:好几种解决方案

    跨域分为广义跨域和狭义跨域 广义跨域:一个域下的文档或脚本试图去请求另一个域下的资源; 广义跨域可以分为以下几种:...

  • ajax readystatus=0;status=0 报错

    跨域 跨域 跨域 一定要找运维或者后台解决

  • 深入跨域问题(2) - 利用 CORS 解决跨域

    阅读目录: 深入跨域问题(1) - 初识 CORS 跨域资源共享;深入跨域问题(2) - 利用 CORS 解决跨域...

  • 响应头设置跨域和Spring注解跨域

    CORS跨域原理详解Spring解决跨域 响应头设置跨域 Spring注解跨域@CrossOrigin 可添加到方...

网友评论

    本文标题:13.跨域两三事

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