学习:什么是浏览器跨域?以及了解实现跨域的七种方法

一、什么是跨域

1.定义**

跨域是指从一个域名的网页去请求另一个域名的资源。它是由浏览器的同源策略造成的,是浏览器为了提高网站的安全性对JavaScript施加的限制,使浏览器不能执行其他网站的脚本。跨域的更加严格的定义是:只要 协议,域名,端口有任何一个的不同,就被当作是跨域。

2.同源策略**

同源策略又分为以下两种

  • DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
    XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。
  • 只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作

我们主要讲第二种,垮域的产生来源于现代浏览器所通用的‘同源策略’。在发送ajax请求时,只有在当前页面地址与请求地址的协议+域名+端口号相同时才允许访问,否则会被拦截。

http://www.a.com/a.js 和 http://www.a.com/b.js 同一域名,非跨域
http://www.a.com:8000/a.js 和 http://www.a.com:8081/b.js 同一域名,不同端口(8081/8000),跨域
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/index.html 和 https://www.123.com/server.php 协议不同(http/https),跨域
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。

二、为什么需要限制浏览器跨域访问?

主要是出于安全的考虑
1

  • 用户访问www.mybank.com ,登陆并进行网银操作,这时cookie啥的都生成并存放在浏览器
  • 用户突然想起件事,并迷迷糊糊地访问了一个邪恶的网站 www.xiee.com
  • 这时该网站就可以在它的页面中,拿到银行的cookie,比如用户名,登陆token等,然后发起对www.mybank.com 的操作。
  • 如果这时浏览器不予限制,并且银行也没有做响应的安全处理的话,那么用户的信息有可能就这么泄露了。
    2
    AJAX同源策略主要用来防止CSRF攻击。如果没有AJAX同源策略,相当危险,我们发起的每一次HTTP请求都会带上请求地址对应的cookie,那么可以做如下攻击:
  • 用户登录了自己的银行页面 http://mybank.com,http://mybank.com向用户的cookie中添加用户标识。
  • 用户浏览了恶意页面 http://evil.com。执行了页面中的恶意AJAX请求代码。
  • http://evil.com向http://mybank.com发起AJAXHTTP请求,请求会默认把http://mybank.com对应cookie也同时发送过去。
  • 银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。
  • 而且由于Ajax在后台执行,用户无法感知这一过程。
    3
    DOM同源策略也一样,如果iframe之间可以跨域访问,可以这样攻击:
  • 做一个假网站,里面用iframe嵌套一个银行网站 http://mybank.com
  • 把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
  • 这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。

三、解决跨域的方法

1.跨域资源共享(CORS)

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。
CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。只需要在后台中加上响应头来允许域请求。

对于客户端,我们正常使用xhr对象发送ajax请求。需要注意的是,需要设置xhr的属性withCredentials为true,不然的话,cookie是带不过去的,设置:
xhr.withCredentials = true
对于服务器端,需要在 response header中设置如下两个字段:
Access-Control-Allow-Origin: http://www.yourhost.com
Access-Control-Allow-Credentials:true
这样就可以跨界请求接口了。

具体的话,可以深入了解阮一峰老师的一篇文章:
http://www.ruanyifeng.com/blog/2016/04/cors.html

2. jsonp实现跨域

JSONP是JSON with Padding(填充式json)的简写。JSONP包含两部分:回调函数和数据
回调函数是当响应到来时要放在当前页面被调用的函数。
数据就是传入回调函数中的json数据,也就是回调函数的参数了。
基本原理就是通过动态创建script标签,然后利用src属性进行跨域。例子:

function handleResponse(response){
 console.log('The responsed data is: '+response.data);
}
var script = document.createElement('script');
script.src = 'http://www.baidu.com/json/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
/*handleResonse({"data": "zhe"})*/
//原理如下:
//当我们通过script标签请求时
//后台就会根据相应的参数(json,handleResponse)
//来生成相应的json数据(handleResponse({"data": "zhe"}))
//最后这个返回的json数据(代码)就会被放在当前js文件中被执行
//至此跨域通信完成

3.通过修改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设置成自身或更高一级的父域,且主域必须相同。
例如:a.b.example.com 中某个文档的document.domain 可以设成a.b.example.com、b.example.com 、example.com中的任意一个,但是不可以设成 c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。
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://example.com/b.html 中也设置document.domain:

<script type="text/javascript">
   document.domain = 'example.com';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>

4.使用window.name来进行跨域**

window.name跨域同样是受到同源策略限制,父框架和子框架的src必须指向统一域名。window.name的优势在于,name的值在不同的页面(或者不同的域名),加载后仍然存在,除非你显示的更改。并且支持的长度达到2M.

//a页面的代码
<script type="text/javascript">
   iframe = document.createElement('iframe');
   iframe.style.display = 'none';
   var state = 0;

   iframe.onload = function() {
     if(state === 1) {
         var data = iframe.contentWindow.name;
         console.log(data);
         iframe.contentWindow.document.write('');
         iframe.contentWindow.close();
         document.body.removeChild(iframe);
     } else if(state === 0) {
         state = 1;
         iframe.contentWindow.location = 
         'http://m.zhuanzhuan.58.com:8887/b.html';
     }
   };
   document.body.appendChild(iframe);
</script>
//b页面代码
<script type="text/javascript">
   window.name = "hello";
</script>

5.使用HTML5的window.postMessage方法跨域**

window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。
调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * 。需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。
上面所说的向其他window对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。

  1. a.com/index.html中的代码:
<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
 var ifr = document.getElementById('ifr');
 var targetOrigin = 'http://b.com'; // 若写成'http://b.com/c/proxy.html'效果一样
          // 若写成'http://c.com'就不会执行postMessage了
 ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>
  1. b.com/index.html中的代码:
<script type="text/javascript">
 window.addEventListener('message', function(event){
  // 通过origin属性判断消息来源地址
  if (event.origin == 'http://a.com') {
   alert(event.data); // 弹出"I was there!"
   alert(event.source); // 对a.com、index.html中window对象的引用
         // 但由于同源策略,这里event.source不可以访问window对象
  }
 }, false);
</script>

6.web sockets**

web sockets是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用)
web sockets原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。

只有在支持web socket协议的服务器上才能正常工作。

var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
 var data = event.data;
}

7. location.hash跨域**

原理是利用location.hash来进行传值。假设域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html传递信息。

  1. cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html页面
  2. cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据
  3. 同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一旦有变化则获取获取hash值
    注:由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe。例子:

先是a.com下的文件cs1.html文件:

function startRequest(){ 
var ifr = document.createElement('iframe'); 
ifr.style.display = 'none'; 
ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo'; 
document.body.appendChild(ifr);
} 
function checkHash() {
try { 
 var data = location.hash ? location.hash.substring(1) : ''; 
  if (console.log) { 
    console.log('Now the data is '+data); 
   }
    } catch(e) {};
    }setInterval(checkHash, 2000);

cnblogs.com域名下的cs2.html:

//模拟一个简单的参数处理操作
switch(location.hash){
 case '#paramdo':  
 callBack(); 
  break; 
  case '#paramset': 
   //do something……  
   break;
   } 
  function callBack(){
   try {  parent.location.hash = 'somedata';
    } catch (e) {
      // ie、chrome的安全机制无法修改
      parent.location.hash,  
      // 所以要利用一个中间的cnblogs域下的代理
      iframe  var ifrproxy = document.createElement('iframe');  
      ifrproxy.style.display = 'none';  
      ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; 
      // 注意该文件在"a.com"域下  document.body.appendChild(ifrproxy); 
      }
      }

a.com下的域名cs3.html

//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);

参考:跨域那些事儿

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值