夜间正能量网站入口网址不用下载,世代网络网站建设设计,长三角旅游推广联盟,wordpress 插件开发深入理解前端跨域方法和原理
前言
受浏览器同源策略的限制#xff0c;本域的js不能操作其他域的页面对象#xff08;比如DOM#xff09;。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。所以我们要通过一些方法使本域的js能够操作其他域的页面对象或者使…深入理解前端跨域方法和原理
前言
受浏览器同源策略的限制本域的js不能操作其他域的页面对象比如DOM。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。所以我们要通过一些方法使本域的js能够操作其他域的页面对象或者使其他域的js能操作本域的页面对象iframe之间。
这里需要明确的一点是所谓的域跟js的存放服务器没有关系比如baidu.com的页面加载了google.com的js那么此js的所在域是baidu.com而不是google.com。也就是说此时该js能操作baidu.com的页面对象而不能操作google.com的页面对象。
跨域的方法总结
单向跨域一般用于获取数据
一、使用JSONP跨域
原理因为通过script标签引入的js是不受同源策略的限制的正如前文提到的baidu.com的页面加载了google.com的js。所以我们可以通过script标签引入一个js或者是一个其他后缀形式如PHPjsp等的文件此文件返回一个js函数的调用如返回JSONP_getUsers([paco,john,lili])也就是说此文件返回的结果调用了JSONP_getUsers函数并且把[paco,john,lili]传进去这个[paco,john,lili]是一个用户列表。那么如果此时我们的页面中有一个JSONP_getUsers函数那么JSONP_getUsers就被调用到并且传入了用户列表。此时就实现了在本域获取其他域数据的功能也就是跨域。 实现例子如下 前端引入远程js并定义好JSONP_getUsers函数注意需要先定义好JSONP_getUsers函数避免在远程js加载完成并调用JSONP_getUsers时此函数不存在 [html] view plain copy print? 1.//本域为baidu.com 2.script 3. function JSONP_getUsers(users){ 4. console.dir(users); 5. } 6./script 7.//加载google.com的getUsers.php 8.script srchttp://www.google.com/getUsers.php/script 需要google.com提供支持getUsers.php代码如下 [html] view plain copy print? 1.?php 2. echo JSONP_getUsers([paco,john,lili]);//返回一个js函数的调用 3.? 为什么script标签引入的文件不受同源策略的限制因为script标签引入的文件内容是不能够被客户端的js获取到的不会影响到被引用文件的安全所以没必要使script标签引入的文件遵循浏览器的同源策略。而通过ajax加载的文件内容是能够被客户端js获取到的所以ajax必须遵循同源策略否则被引入文件的内容会泄漏或者存在其他风险。
JSONP的缺点则是它只支持GET请求而不支持POST等其它类型的HTTP请求虽然采用post动态生成iframe是可以达到post跨域的目的但这样做是一个比较极端的方式不建议采用。一般get请求能完成所有功能。比如如果需要给其他域服务器传送参数可以在请求后挂参数注意不要挂隐私数据即 [html] view plain copy print? 1.script srchttp://www.google.com/getUsers.php?flagdotime1/script。 JSONP易于实现但是也会存在一些安全隐患如果第三方的脚本随意地执行那么它就可以篡改页面内容截获敏感数据。但是在受信任的双方传递数据JSONP是非常合适的选择。可以看出来JSONP跨域一般用于获取其他域的数据。
一般能够用JSONP实现跨域就用JSONP实现这也是前端用的最多的跨域方法。
二、动态创建script标签
这种方法其实是JSONP跨域的简化版JSONP只是在此基础上加入了回调函数。 比如上例中的getUsers.php返回的如果不是一个js函数的调用而是一个js变量如 [html] view plain copy print? 1.?php 2. echo var users[paco,john,lili];//返回一个js变量users 3.? 那么在本域下就可以取到data变量这里需要注意判断script节点是否加载完毕如 [html] view plain copy print? 1.js.onload js.onreadystatechange function() { 2. if (!this.readyState || this.readyState loaded || this.readyState complete) { 3. console.log(users);//此处取出其他域的数据 4. js.onload js.onreadystatechange null; 5. } 6.}; 三、flash URLLoader
flash有自己的一套安全策略服务器可以通过crossdomain.xml文件来声明能被哪些域的SWF文件访问SWF也可以通过API来确定自身能被哪些域的SWF加载。当跨域访问资源时例如从域baidu.com请求域google.com上的数据我们可以借助flash来发送HTTP请求。首先修改域google.com上的crossdomain.xml(一般存放在根目录如果没有需要手动创建) 把baidu.com加入到白名单。其次通过Flash URLLoader发送HTTP请求最后通过Flash API把响应结果传递给JavaScript。Flash URLLoader是一种很普遍的跨域解决方案不过需要支持iOS的话这个方案就不可行了。
四、Access Control
此跨域方法目前只在很少的浏览器中得以支持这些浏览器可以发送一个跨域的HTTP请求Firefox, Google Chrome等通过XMLHTTPRequest实现IE8下通过XDomainRequest实现请求的响应必须包含一个Access- Control-Allow-Origin的HTTP响应头该响应头声明了请求域的可访问权限。例如baidu.com对google.com下的getUsers.php发送了一个跨域的HTTP请求通过ajax那么getUsers.php必须加入如下的响应头 [html] view plain copy print? 1.header(Access-Control-Allow-Origin: http://www.baidu.com);//表示允许baidu.com跨域请求本文件 五、window.name
window 对象的name属性是一个很特别的属性当该window的location变化然后重新加载它的name属性可以依然保持不变。那么我们可以在页面 A中用iframe加载其他域的页面B而页面B中用javascript把需要传递的数据赋值给window.nameiframe加载完成之后iframe.onload页面A修改iframe的地址将其变成同域的一个地址然后就可以读出iframe的window.name的值了因为A中的window.name和iframe中的window.name互相独立的所以不能直接在A中获取window.name而要通过iframe获取其window.name。这个方式非常适合单向的数据请求而且协议简单、安全。不会像JSONP那样不做限制地执行外部脚本。
六、服务器代理
在数据提供方没有提供对JSONP协议或者 window.name协议的支持也没有对其它域开放访问权限时我们可以通过server proxy的方式来抓取数据。例如当baidu.com域下的页面需要请求google.com下的资源文件getUsers.php时直接发送一个指向 google.com/getUsers.php的Ajax请求肯定是会被浏览器阻止。这时我们在baidu.com下配一个代理然后把Ajax请求绑定到这个代理路径下例如baidu.com/proxy/, 然后这个代理发送HTTP请求访问google.com下的getUsers.php跨域的HTTP请求是在服务器端进行的服务器端没有同源策略限制客户端并没有产生跨域的Ajax请求。这个跨域方式不需要和目标资源签订协议带有侵略性。
双向跨域两个iframe之间或者两个页面之间一般用于获取对方数据document.domain方式还可以直接操作对方DOM
七、document.domain两个iframe之间
通过修改document的domain属性我们可以在域和子域或者不同的子域之间通信。同域策略认为域和子域隶属于不同的域比如baidu.com和 youxi.baidu.com是不同的域这时我们无法在baidu.com下的页面中调用youxi.baidu.com中定义的JavaScript方法。但是当我们把它们document的domain属性都修改为baidu.com浏览器就会认为它们处于同一个域下那么我们就可以互相获取对方数据或者操作对方DOM了。
问题 1、安全性当一个站点被攻击后另一个站点会引起安全漏洞。 2、如果一个页面中引入多个iframe要想能够操作所有iframe必须都得设置相同domain。
八、location.hash两个iframe之间又称FIMFragment Identitier Messaging的简写
因为父窗口可以对iframe进行URL读写iframe也可以读写父窗口的URLURL有一部分被称为hash就是#号及其后面的字符它一般用于浏览器锚点定位Server端并不关心这部分应该说HTTP请求过程中不会携带hash所以这部分的修改不会产生HTTP请求但是会产生浏览器历史记录。此方法的原理就是改变URL的hash部分来进行双向通信。每个window通过改变其他 window的location来发送消息由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值所以要借助于父窗口域名下的一个代理iframe并通过监听自己的URL的变化来接收消息。这个方式的通信会造成一些不必要的浏览器历史记录而且有些浏览器不支持onhashchange事件需要轮询来获知URL的改变最后这样做也存在缺点诸如数据直接暴露在了url中数据容量和类型都有限等。下面举例说明
假如父页面是baidu.com/a.htmliframe嵌入的页面为google.com/b.html此处省略了域名等url属性要实现此两个页面间的通信可以通过以下方法。
1、a.html传送数据到b.html
1 a.html下修改iframe的src为google.com/b.html#paco 2 b.html监听到url发生变化触发相应操作
2、b.html传送数据到a.html由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值所以要借助于父窗口域名下的一个代理iframe
1 b.html下创建一个隐藏的iframe此iframe的src是baidu.com域下的并挂上要传送的hash数据如srchttp://www.baidu.com/proxy.html#data 2 proxy.html监听到url发生变化修改a.html的url因为a.html和proxy.html同域所以proxy.html可修改a.html的url hash 3 a.html监听到url发生变化触发相应操作
b.html页面的关键代码如下 [html] view plain copy print? 1.try { 2. parent.location.hash data; 3.} catch (e) { 4. // ie、chrome的安全机制无法修改parent.location.hash 5. var ifrproxy document.createElement(iframe); 6. ifrproxy.style.display none; 7. ifrproxy.src http://www.baidu.com/proxy.html#data; 8. document.body.appendChild(ifrproxy); 9.} proxy.html页面的关键代码如下 [html] view plain copy print? 1.//因为parent.parent即baidu.com/a.html和baidu.com/proxy.html属于同一个域所以可以改变其location.hash的值 2.parent.parent.location.hash self.location.hash.substring(1); 九、使用HTML5的postMessage方法两个iframe之间或者两个页面之间
高级浏览器Internet Explorer 8, chromeFirefox , Opera 和 Safari 都将支持这个功能。这个功能主要包括接受信息的message事件和发送消息的postMessage方法。比如baidu.com域的A页面通过iframe嵌入了一个google.com域的B页面可以通过以下方法实现A和B的通信
A页面通过postMessage方法发送消息 [html] view plain copy print? 1.window.onload function() { 2. var ifr document.getElementById(ifr); 3. var targetOrigin http://www.google.com; 4. ifr.contentWindow.postMessage(hello world!, targetOrigin); 5.}; postMessage的使用方法
otherWindow.postMessage(message, targetOrigin);
otherWindow: 指目标窗口也就是给哪个window发消息是 window.frames 属性的成员或者由 window.open 方法创建的窗口 message: 是要发送的消息类型为 String、Object (IE8、9 不支持) targetOrigin: 是限定消息接收范围不限制请使用 *
B页面通过message事件监听并接受消息: [html] view plain copy print? 1.var onmessage function (event) { 2. var data event.data;//消息 3. var origin event.origin;//消息来源地址 4. var source event.source;//源Window对象 5. if(originhttp://www.baidu.com){ 6.console.log(data);//hello world! 7. } 8.}; 9.if (typeof window.addEventListener ! undefined) { 10. window.addEventListener(message, onmessage, false); 11.} else if (typeof window.attachEvent ! undefined) { 12. //for ie 13. window.attachEvent(onmessage, onmessage); 14.} 同理也可以B页面发送消息然后A页面监听并接受消息。
总结
跨域的方法很多不同的应用场景我们都可以找到一个最合适的解决方案。比如单向的数据请求我们应该优先选择JSONP或者window.name双向通信优先采取location.hash在未与数据提供方达成通信协议的情况下我们也可以用server proxy的方式来抓取数据。