前端开发中的跨域问题

在开始之前,我们先熟悉这样一个概念:同源策略。所谓同源策略,指的是‘同一个协议,同一个域名,同一个端口’。三者有任意一个不一样,均不可称之为同源。

URL 说明 是否允许请求

http://www.a.com/a.js

http://www.a.com/b.js

同一协议,同一域名,同一端口

http://www.a.com/a.js

https://www.a.com/b.js

不同协议,同一域名,同一端口

http://www.a.com/a.js

https://www.b.com/b.js

不同协议,不同域名,同一端口

http://www.a.com/a.js

https://www.a.com:8000/b.js

不同协议,不同域名,不同端口

http://www.a.com/a.js

http://www.b.com/b.js

同一协议,不同域名,同一端口

http://www.a.com/a.js

http://www.a.com:8000/b.js

同一协议,同一域名,不同端口

http://www.a.com/a.js

http://192.168.98.123/b.js

域名与域名对应ip

http://www.a.com/a.js

http://nba.a.com/b.js

主域相同,子域不同

http://www.a.com/a.js

http://a.com/b.js

同一域名,不同二级域名

 

从上边可以看出,引起跨域问题的场景很多。我们接下来就简单的来说一下跨域问题的存在及其解决方案。

避开跨域

比如在a.com域名下的某个页面需要跨域请求b.com下的资源文件。这时候我们可以把执行跨域的js文件放到b.com下。而在a.com中通过外联的方式引入b.com下的文件,这样js文件与图片等资源都在同一域名下,即可正常访问。使用场景比如:cdn,图像……

document.domain + iframe

对于主域相同,子域不同引起的跨域问题,我们可以通过document.domain的方式来解决。

具体可以在http://www.a.com/a.html和http://script.a.com/b.html中分别设置:document.domain = 'a.com',然后在a.html中创建一个iframe,这样就可以实现通信了。

http://www.a.com/a.html 部分设置如下:

document.domain = 'a.com';

function addIframe () {
  var iframe = document.createElement('iframe');
  iframe.src = 'http://script.a.com/b.html';
  iframe.style.display = 'none';
  docuemnt.body.append(iframe);
  iframe.onload = function () {
    var ele = iframe.contentDocument || iframe.contentWindow.docuemnt;
    console.log(ele.getElementsByTagName('h1')[0].childNodes[0].nodeValue);
  }
}

http://script.a.com/b.html 部分设置如下:

document.domain = 'a.com';

 那么问题来了:

  • 安全性:当一个站点收到攻击后,另外一个站点也会引起安全泄露
  • 耦合性:当页面中有多个iframe时,要想能操作所有iframe,则必须设置相同的document.domain

location.hash + iframe

由于地址栏上的hash值发生改变,并不会触发页面刷新,所以可以利用这个原理来跨域请求数居。但是数据大小有限制。

假设a.com域名下的文件a.html需要跨域请求b.com下的文件b.html。采用location.hash来处理如下:在a.html中创建一个隐藏的iframe,设置src指向b.com下的b.html,把hash值当做参数传递过去。b.html在接收到请求后,通过修改a.html上的hash值来传递数据。然后在a.html上设置一个定时监测其地址栏hash值变化的定时器即可。

a.html部分代码如下:

function addFrame () {
  var ifr = document.createElement("iframe");
  ifr.src = 'http://www.b.com/b.html#proto';
  ifr.style.display = "none";
  document.body.append(ifr);
}

function checkHash() {
  window.addEventListener("hashchange",function(e){ 
     var hash = window.location.hash ? window.location.hash.subString(1) :"";
     console.log("你请求到的信息是:"+ hash);	    
  });
}

setInterval(checkHah,1000);

b.html 部分代码如下:

if(location.hash === '#proto'){
  var data = "要返回给a.html的数据";
  try{
    parent.location.hash = data ;
  }catch(e){
    // ie、chrome的安全机制无法修改parent.location.hash,
    // 所以要利用一个中间的cnblogs域下的代理iframe
    var ifr = document.createElement("iframe");
    ifr.src="http://www.a.com/test.html#"+data;
    ifr.style.display = "none";
    document.body.append(ifr);
  }
}

test.html 部分代码如下:

parent.parent.location.href = location.hash.substring(1);

  这样做也会产生一些问题:

  • 数据直接暴露在地址栏
  • 数据传输量有限
  • 对浏览器历史记录产生影响
  • 地址栏上传输汉字问题
  • 步骤繁琐

postMessage

postMessag是html5新增的API。用于支持web的实时消息传递

使用方法:otherWindow.postMessage(message, targetOrigin);

  • otherWindow:对发送信息页面的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
  • message:要发送的消息
  • targetOrigin:接受消息的窗口,设置为*则不做限制。

a.com/a.html 部分代码如下:

<iframe id="ifr" src="http://www.b.com/b.html"></iframe>

<script>
  var ifr = document.getElementById("ifr"),
        url = "http://www.b.com";
  ifr.contentWindow.postMessage("hello world !",url); </script>

 www.b.com/b.html 部分代码如下:

window.addEventListener("message",function(e){
  if(e.origin === 'http://a.com'){
    console.log(e.data)
  }
},false)

动态创建script

尽管浏览器有同源策略的限制。但是通过script标签的src属性却可以访问其它域名下的文件,并可以执行其中的函数。下边先看一下判断js加载是否完毕的方法:

js.onload = js.onreadystatechange = function() {
  if(!this.readyState || this.readyState === "loaded" || this.readyState ==="complete"){
    js.onload = js.onreadystatechange = null;
  }
}

JSONP

创建一个回调函数,在远程服务器上执行这个函数,并把json数据当做参数传递。

JSONP原理

function addScript (src) {
  var spt = document.createElement("script");
  spt.setAttribute("type","text/javascript");
  spt.src= src;
  document.body.append(spt);
}

function handleFn(data){
  //处理跨域回调数据
}

window.onload = function() {
  addScript("http://localhost:2000/MyService.ashx?callback=handleFn")
}

$.getJSON

先看一下,getJSON方法在跨域问题中的使用方法:

$.getJSON("http://localhost:20002/MyService.ashx?callback=?",function(data) {
  //处理回调函数
})

像上面那样,我们看到了一个:callback=?。这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个问号是内部自动生成的一个回调函数名。那么问题是:如果服务器上规定了回调函数名,我们应该怎么处理呢?答案是:ajax。

ajax

 

$.ajax({
  url:"http://localhost:20002/MyService.ashx?callback=?",
  dataType:"jsonp",
  jsonpCallback:"person",
  success:function(data) {
   //处理回调函数
  }
})

jsonpCallback就是可以指定我们自己的回调方法名person,远程服务接受callback参数的值就不再是自动生成的回调名,而是person。dataType是指定按照JSOPN方式访问远程服务。

接下来才是我们这篇文章的重点。

跨域资源共享(CORS)

用于授权资源的跨域访问 。比如在前端开发中,A站点需要访问B站点下的某一个接口,从而得到我们想要的数据,这个时候涉及到一个重要的概念:Access-Control-Allow-Origin。如果服务器端在返回头中没有设置这个属性,或者设置的属性值不包括我们当前的域名A,那么这个时候,当前域名A是不被服务器允许进行跨域资源访问的。

 

上边这幅图描述了一个完整的CORS请求过程。

CORS请求的过程如下:

  1. 首先判断是不是简单请求,如果是,如:get请求,只需要在HTTP Response后添加Access-Control-Allow-Origin;
  2. 如果不是简单请求,如POST,PUT,DELETE等,这个时候浏览器会分2次进行请求:一次是预检请求preflight(method: OPTIONS),主要用于检测请求来源是否合法,并返回Header。第二次才是真正的请求,所以服务器必须处理OPTIONS应答。

服务器处理请求解析如下:

  1. 首先检测Http头部中是否有origin字段信息
  2. 如果没有或者不被允许,则当做普通请求处理,结束;
  3. 如果有并且是在允许范围内,则判断是否是复杂请求;
  4. 如果是复杂请求,则先执行预检请求preflight(method: OPTIONS),返回Allow-Headers、Allow-Methods等,内容为空;进如步骤6;
  5. 如果是简单请求,则直接进步步骤6;
  6. 执行请求,返回Allow-Origin、Allow-Credentials等,并返回正常内容。 


在HTML5中,也有一些元素为CORS提供了支持,如img,video。此时需要设置crossOrigin属性,属性值可以是anonymoususe-credentials。比如我们要用canvas访问跨域图片,就可以像下边这样操作:

var img = new Image(),
      canvas = document.createElement("canvas"),
      cxt = canvas.getContext("2d");

img.crossOrigin = "Anonymous";//使用CORS
img.src="http://img5.imgtn.bdimg.com/it/u=104961686,3757525983&fm=27&gp=0.jpg";

img.onload = function() {
  canvas.width = img.width; canvas.height = img.height; cxt.drawImage(img,0,0);
document.body.append(canvas); localStorage.setItem("imgData",canvas.toDataURL("image/jpg")) }

  

posted on 2018-03-02 10:30  李老头  阅读(1918)  评论(0编辑  收藏  举报

导航