跨域的几种解决办法

跨域的几种解决办法

一、什么是跨域

由于JavaScript对安全访问因素的考虑,是不允许js跨域调用其他页面的,这里的域我们把它想象成域名,如,一个域名为https://www.oschina.net,另外一个域名为https://www.zhihu.com,这两者属于不同的域名,它们之间的页面也是不能相互调用的,它属于同源策略所定义限制中的一种。同源策略具体分为以下几类:

  • 不同域名
  • 相同域名不同端口号,如https://www.oschina.net:8000和https://www.oschina.net:8001
  • 同一个域名不同协议,如http://www.oschina.net和https://www.oschina.net
  • 域名和域名对应的的IP,如http://b.qq.com和 http://10.198.7.85
  • 主域和子域,如http://www.oschina.net和https://test.oschina.net
  • 子域和子域,如https://test1.oschina.net和https://test2.oschina.net


以上情况只要出现了,那么就会产生跨域问题。那么如果解决跨域问题呢,下面的小节会总结一些解决跨域常用的方法。

二、跨域解决方案

1、JSONP

对于JSONP,有个通俗易懂的解释-JSONPJSON with Padding)是数据格式JSON的一种“使用模式”,可以让网页从别的网域要数据。
由于同源策略,如上所述。但是(中国人讲话是很有文化的),HTML的 <script>元素是一个例外,它并不遵循同源策略,利用 <script>元素的这个开放策略,网页可以得到从其他来源动态产生的JSON数据,而这种使用模式就是所谓的 JSONP。用JSONP抓到的数据并不是JSON,而是任意的JavaScript,用 JavaScript解释器运行而不是用JSON解析器解析。来来来,我来举个栗子?吧
前端浏览器页面


      
      
  1. <script>
  2. function jsonpCallBack (res, req) {
  3. console. log(res, req);
  4. }
  5. </script>
  6. <script type="text/JavaScript" src="http://localhost/test/jsonp.php?callback=jsonpCallBack&data=getJsonpData"> </script>

另一个域名服务器请求接口


      
      
  1. <?php
  2. /*后端获取请求字段数据,并生成返回内容*/
  3. $data = $_GET[ "data"];
  4. $callback = $_GET[ "callback"];
  5. echo $callback. "('success', '". $data. "')";
  6. ?>

测试结果如下
105223_4q5i_2912341.png
105319_jqRN_2912341.png
这种方案需要注意的是他支持GET这一种HTTP请求类型,还有尤为重要的就是其他域要有一定可靠性,不然你的网站会GG的。当然有时我们还可以通过一个方法来动态生成需要的JSONP

2、跨域资源共享(CORS-Cross Origin Resource Sharing)

CORS,它是JSONP模式的现代升级版,与JSONP不同的是,CORS除了GET要求方法以外也支持其他的 HTTP要求。浏览器CORS请求分成两种
a、简单请求
b、协商模型/预检请求(Preflighted Request),即非简单请求
如何区分请求具体属于哪一种呢,下面我总结了几点:
1) 请求方式

  • GET
  • HEAD
  • POST

2)HTTP的头信息子段

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain,其中'text/plain'默认支持,其他两种则需要预检请求和服务器协商。

满足以上两大点的即为简单请求,否则为非简单请求。具体请求处理的不同,大家可以去查阅下MDN https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS ,那里有详细的解析及用法。

3、document.domain+iframe(适用于主域名相同的情况)

从上面的同源策略我们可以知道,浏览器这边是认为主域和子域、子域和子域,它们属于不同的域,那么我们如果需要让主域和子域之间可以进行通信,需要做的就是通过修改document.domain,把它们改成相同的domain

在域名为server.example.com中的a.html


      
      
  1. document.domain = 'example.com';
  2. var $iframe = document.createElement( 'iframe');
  3. $iframe.src = 'server.child.example.com/b.html';
  4. $iframe.style.display = 'none';
  5. document.body.appendChild( $iframe);
  6. $iframe.onload = function(){
  7. var doc = $iframe.contentDocument || $iframe.contentWindow.document;
  8. //在这里操作doc,也就是操作b.html
  9. $iframe.onload = null;
  10. };

在域名为server.child.example.com中的b.html

document.domain = 'example.com'
      
      

这种形式方便归方便,但也有其方便带来的隐患

  • 安全性,当一个站点被攻击后,另一个站点会引起安全漏洞。
  • 若页面中引入多个iframe,要想操作所有iframe,domain需要全部设置成一样的。

4、window.name + iframe

window 对象的name属性是一个很特别的属性,它可以在不同页面甚至不同域名加载后依旧存在。使用步骤如下:
step1 - 首先在页面A中利用iframe加载其他域中的页面B
step2 - 在页面B中将需要传递的数据赋给window.name
step3 - iframe加载完成后,页面A中修改iframe地址,将其变成同一个域下的地址,然后获取iframe中页面B的window.name 属性

示例代码如下:
首先我们在域名为http://127.0.0.1下建立好B页面,在B页面的<script>标签中将需要传递的数据赋给window.name

window.name = '页面B中传递给页面A的数据';
      
      

然后我们域名为http://127.0.0.1:9000的A页面,这里我们需要做的一件事就是利用iframe加载页面B,并将其域名进行修改,变成和页面A一样的域名。


      
      
  1. function proxy (url, callback) {
  2. var flag = true,
  3. $iframe = document.createElement( 'iframe'),
  4. loadCallBack = function () {
  5. if (flag) {
  6. // 这里我们还得在域名为 http://127.0.0.1:9000 建立一个tmp.html文件当做缓存界面
  7. $iframe.contentWindow.location = 'http://127.0.0.1:9000/tmp.html';
  8. flag = false;
  9. }
  10. // 修改localtion后,每次触发onload事件会重置src,相当于重新载入页面,然后继续触发onload。
  11. // 这里是针对该问题做的处理
  12. else {
  13. callback( $iframe.contentWindow.name);
  14. $iframe.contentWindow.close();
  15. document.body.removeChild( $iframe);
  16. $iframe.src = '';
  17. $iframe = null;
  18. }
  19. };
  20. $iframe.src = url;
  21. $iframe.style.display = 'none';
  22. // 事件绑定兼容简单处理
  23. // IE 支持iframe的onload事件,不过是隐形的,需要通过attachEvent来注册
  24. if ( $iframe.attachEvent) {
  25. $iframe.attachEvent( 'onload', loadCallBack);
  26. }
  27. else {
  28. $iframe.onload = loadCallBack;
  29. }
  30. document.body.appendChild( $iframe);
  31. }
  32. proxy( 'http://127.0.0.1/bop/test.html', function(data){
  33. console.log(data);
  34. });

测试结果如图
173348_l7Ik_2912341.png

5、HTML5中的postMessage(适用于两个iframe或两个页面之间)

postMessage隶属于html5,但是它支持IE8+和其他浏览器,可以实现同域传递,也能实现跨域传递。它包括发送消息postMessage和接收消息message功能。

postMessage调用语法如下

otherWindow.postMessage(message, targetOrigin, [transfer]);
      
      
  • otherWindow : 其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。
  • message : 将要发送到其他 window的数据,类型为string或者object。
  • targetOrigin : 通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。
  • transfer (可选) : 一串和message 同时传递的 Transferable 对象。

接收消息message 的属性有:

  • data :从其他 window 中传递过来的数据。
  • origin :调用 postMessage  时消息发送方窗口的 origin 。
  • source :对发送消息的窗口对象的引用。

示例如下:域名http://127.0.0.1:9000页面A通过iframe嵌入了http://127.0.0.1页面B,接下来页面A将通过postMessage对页面B进行数据传递,页面B将通过message属性接收页面A的数据

页面A发送消息代码


      
      
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>页面A </title>
  6. </head>
  7. <body>
  8. <h1>hello jsonp </h1>
  9. <iframe src="http://127.0.0.1/b.html" id="iframe"> </iframe>
  10. </body>
  11. </html>
  12. <script>
  13. window. onload = function( ) {
  14. var $iframe = document. getElementById( 'iframe');
  15. var targetOrigin = "http://127.0.0.1";
  16. $iframe. contentWindow. postMessage( 'postMessage发送消息', targetOrigin);
  17. };
  18. </script>

页面B接收消息代码


      
      
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>页面B </title>
  6. </head>
  7. <body>
  8. <h1>hello jsonp </h1>
  9. </body>
  10. </html>
  11. <script>
  12. var onmessage = function ( event) {
  13. var data = event. data; //消息
  14. var origin = event. origin; //消息来源地址
  15. var source = event. source; //源Window对象
  16. if(origin === "http://127.0.0.1:9000"){
  17. console. log(data, origin, source);
  18. }
  19. };
  20. // 事件兼容简单处理
  21. if ( window. addEventListener) {
  22. window. addEventListener( 'message', onmessage, false);
  23. }
  24. else if ( window. attachEvent) {
  25. window. attachEvent( 'onmessage', onmessage);
  26. }
  27. else {
  28. window. onmessage = onmessage;
  29. }
  30. </script>

运行结果如下
193619_tKeQ_2912341.png

6、location.hash + iframe(适用于两个iframe之间)

对于location.hash,我们先看一张图先
195631_IwM0_2912341.png

相信看完图,大家也大概清楚了location.hash到底是用来干啥子的。没错,它可以用来获取或设置页面的标签值 如http://127.0.0.1:9000/#hello ,它的location.hash值则为'#hello'。它一般用于浏览器锚点定位,HTTP请求过程中却不会携带hash,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录,这对我们进行跨域通信给予了帮助。我们可以通过修改URL的hash部分来进行双向通信。

示例如下:域名http://127.0.0.1:9000页面A通过iframe嵌入了http://127.0.0.1页面B,接下来页面A和页面B将通过location.hash进行双向通信。

页面A代码


      
      
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>页面A </title>
  6. </head>
  7. <body>
  8. <iframe src="http://127.0.0.1/bop/test.html#locationHash" id="ifr"> </iframe>
  9. </body>
  10. </html>

页面B代码


      
      
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>页面B </title>
  6. </head>
  7. <body>
  8. <h1>hello localtionHash </h2>
  9. </body>
  10. </html>
  11. <script>
  12. try {
  13. parent. location. hash = 'data';
  14. } catch (e) {
  15. // ie、chrome的安全机制无法修改parent.location.hash,所以要借助于父窗口域名下的一个代理iframe
  16. var $ifrproxy = document. createElement( 'iframe');
  17. $ifrproxy. style. display = 'none';
  18. // 注意proxy.html必须是域名为 http://127.0.0.1:9000 下的页面
  19. $ifrproxy. src = "http://127.0.0.1:9000/proxy.html#locationHashChange";
  20. document. body. appendChild($ifrproxy);
  21. }
  22. </script>

代理页面代码


      
      
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Proxy页面 </title>
  6. </head>
  7. <body>
  8. </body>
  9. <script>
  10. // 因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
  11. parent. parent. location. hash = self. location. hash. substring( 1);
  12. </script>
  13. </html>

运行结果如图
202450_CMjb_2912341.png

转载于:https://my.oschina.net/u/3205969/blog/914674

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值