跨域解决方案汇总

最新博客站点:欢迎来访javascript

1、同源与同源策略html

       咱们知道,同源指的是协议、域名、端口号所有相同。同源策略(Same Origin Policy)是一种约定,它是浏览器最核心也是最基本的安全功能,若是缺乏了同源策略,则浏览器的正常功能均可能会受到影响。Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略是处于对用户安全的考量的,若是缺乏了同源的限制,那又怎么可以肯定别人的网站始终对你是友好的呢。针对非同源的状况制定了一些限制条件,1. 没法读取不一样源的cookie、LocalStorage、indexDB。2. 没法得到不一样源的DOM。3. 不能向不一样源的服务器发送Ajax请求。前端

  在浏览器中,<script><img><iframe><link>等标签均可以跨域加载资源,而不受同源策略的限制。事实上,在大多数情境下,咱们常常是须要借用非同源来提供数据的,因此这就要用到跨域方面的技术了。java

2、JSONPajax

       JSONP是指JSON Padding,JSONP是一种非官方跨域数据交换协议,因为script的src属性能够跨域请求,因此JSONP利用的就是浏览器的这个原理,须要通讯时,动态插入一个javascript标签。请求的地址通常带有一个callback参数,假设须要请求的地址为http://localhost:3000?callback=show,服务器返回的代码通常是show()的JSON数据,而show函数偏偏是前端须要用的这个数据的函数。JSONP很是简单易用,自动补全API利用的就是JSONP。json

        一个简单的例子:segmentfault

var script = doxument.createElement("script");
script.setAttribute("type", "text/javascript");
script.src="http://example.com/ip?callback=handleResponse";
document.body.appendChild(script);

function handleResponse(data) {
    console.log('Your public IP address is: '+data.ip);
}

       JSONP解决跨域的本质:<script>标签能够请求不一样域名下的资源,即<script>请求不受浏览器同源策略的影响。上例中的script会向http://example.com/服务器发送请求,这个请求的url后面带了个callback参数,是用来告诉服务器回调方法的方法名的。由于服务器收到请求后,会把相应的数据写进handleResponse的参数,也就是服务器会返回以下的脚本:后端

handleResponse({
    "ip" : "8.8.8.8"
});

       这样浏览器经过<script>下载的资源就是上面的脚本了,<script>下载完就会当即执行,也就是说http://example.com/ip?callback=handleResponse这个请求返回后就会当即执行上面的脚本代码,而这个脚本代码就是调用回调方法和拿到json数据了。跨域

       咱们再来看一个例子:浏览器

//请求代码
function jsonp(callback) {
    var script = document.createElement("script");
        url = `https://localhost:3000?callback=${callback}`;
    script.setAttribute("src", url);
    document.querySelector("head").appendChild(script);
}
function show(data) {
    concole.log(`学生姓名为: ${data.name},年龄为: ${data.age},性别为: ${data.sex}`);
}
jsonp("show");
//响应代码
const student = {
    name: "Knight",
    age: 19,
    sex: "male"
};
var callback = url.parse(req.url, true).query.callback;
res.writeHead(200,{
    "Content-Type": "application/json;charset=utf-8"
});
res.end(`${callback}(${JSON.stringify(student)})`);

  JSONP有一个很大问题,就是只能进行GET请求。

3、跨域源资源共享(CORS)

  CORS是W3C制定的跨站资源分享标准,可让AJAX实现跨域访问,定义了在必须访问跨域资源时浏览器与服务器该如何沟通。CORS背后的基本思想,就是使用自定义的HTTP头部让浏览器和服务器进行沟通,从而决定请求或响应应该成功仍是失败。

        好比一个简单的使用GET或POST的请求,它没有自定义的头部,而主体内容是text/plain。在发送该请求时,须要给它附加一个额外的Origin头部,其中包含请求页面的源信息(协议、域名、端口号),以便服务器根据该头部信息来决定是否给予响应。

Origin: http://www.example.com

  若是服务器认为这个请求能够接受,就在Access-Control-Allow-Origin头部中发回相同的源信息(若是是公共资源,能够发“*”)。例如:

Access-Control-Allow-Origin: http://www.example.com

  若是没有这个头部信息或信息不匹配,浏览器就会驳回请求。正常状况下,浏览器会处理请求。此时,请求和响应都不包含Cookie信息。

  简单请求的跨域:

  请求方式为GET或则POST;

  倘若请求是POST的话,Content-Type必须为下列之一:

    application/x-www-form-urlencoded

    mutipart/form-data

       text/plain

  不含有自定义头;

 对于简单的跨域只进行一次http请求:

function ajaxPost(url, obj, header) {
    return new Promise((resolve, reject) => {
         var xhr=new XMLHttpRequest(),
              str = '' ;
              keys = Object.keys(obj);
         for(var i=0,len=keys.length;i<len;i++) {
             str +=`${keys[i]}=${obj[keys[i]]}&`;
         }
         str = str.substring(0, str.length - 1);
         xhr.open('post', url);
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         if(header instanceof Object) {
             for(var k in header)
                  xhr.setRequestHeader(k, header[k]);
         }
         xhr.send(str);
         xhr.onreadystatechange = function() {
              if(xhr.readyState == 4) {
                  if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                      resolve(xhr.responseText);
                  } else {
                      reject();
                  }
              }
         }
         
    });
}
ajaxPost("https://localhost:3000?page=cors", {
        name: "Knight",
        age: 19,
        sex: "male"
}).then((text) => {console.log(text);},
                ()=>{console.log("请求失败");});
//后端处理
var postData = "";
req.on("data", (data) => {
    postData += data;
});
req.on("end", () => {
    postData = querystring.parse(postData);
    res.writeHead(200, {
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/json;charset=utf-8"
    });
    if(postData.name === student.name && Number(postData.age) === student.age &&  postData.sex === student.sex) {
        res.end(`yeah! ${postData.name} is a good guy!`);
    } else {
        res.end("No! a bad guy!");
    }
});

对于非简单请求来讲,须要两次http请求,其中在请求以前有一次预检请求。

function ajaxPost(url, obj, header) {
    return new Promise((resolve, reject) => {
         var xhr=new XMLHttpRequest(),
              str = '' ;
              keys = Object.keys(obj);
         for(var i=0,len=keys.length;i<len;i++) {
             str +=`${keys[i]}=${obj[keys[i]]}&`;
         }
         str = str.substring(0, str.length - 1);
         xhr.open('post', url);
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         if(header instanceof Object) {
             for(var k in header)
                  xhr.setRequestHeader(k, header[k]);
         }
         xhr.send(str);
         xhr.onreadystatechange = function() {
              if(xhr.readyState == 4) {
                  if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                      resolve(xhr.responseText);
                  } else {
                      reject();
                  }
              }
         }
         
    });
}
ajaxPost("https://localhost:3000?page=cors", {
        name: "Knight",
        age: 19,
        sex: "male"
}, {"X-author": "Knight"}).then((text) => {console.log(text);},
                ()=>{console.log("Request Error!");});
//后端处理
var postData = "";
if(req.method == "OPTIONS") {
    res.writeHead(200, {
        "Access-Control-Max-Age": 3000,
        "Access-Control-Allow-Headers": "X-author",
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/json;charset=utf-8"
    });
    res.end();
    return void 0;
}
req.on("data", (data) => {
    postData += data;
});
req.on("end", () => {
    postData = querystring.parse(postData);
    res.writeHead(200, {
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/json;charset=utf-8"
    });
    if(postData.name === student.name && Number(postData.age) === student.age &&  postData.sex === student.sex) {
        res.end(`yeah! ${postData.name} is a good guy!`);
    } else {
        res.end("No! a bad guy!");
    }
});

  上面代码中,两个响应头: Access-Control-Allow-Headers,用来指明在实际的请求中,可使用那些自定义的http请求头;Access-Control-Max-Age,用来指定这次预请求的结果的有效期,在有效期内则不会发出预请求,相似于缓存。

4、document.domain实现跨域

  能够将子域和主域的document.domian设为同一个主域来实现跨域。但前提条件是,这两个域名必须属于同一个基础域名,所用的协议,端口都要一致,不然没法经过document.domain()来进行跨域。

        example 1:

        若是想要在你的http://www.knightboy.cn/a.html页面里使用<iframe>调用另外一个http://knightboy.cn/b.html页面。这时候你想在a页面里面获取b页面里的DOM,而后进行操做。而后你会发现你不能得到b的DOM。document.getElementById("myIFrame").contentWindow.document或window.parent.document.body由于两个窗口不一样源而报错。

        这时候你只须要在a页面里和b页面里把document.domian设置成相同的值就能够在两个页面里操做Dom了。

        example 2:

        若是你在http://www.knightboy.cn/a.html页面里写入了document.cookie = "test=hello world";你在http://knightboy.cn/b.html页面是拿不到这个cookie的。

        缘由在于,Cookie是服务器写入浏览器的一小段信息,只有同源的网页才能共享。可是,两个网页一级域名相同,二级域名不一样,浏览器容许经过设置document.domain来共享Cookie。另外,服务器也能够在设置Cookie的时候,指定Cookie的所属域名为一级域名。这样的话,二级域名和三级域名不用作任何设置即可以读取这个Cookie。

        有一点须要注意的是:document.domain虽然能够读写,但只能设置成自身或者是高一级的父域且主域必须相同。因此只能解决一级域名相同二级域名不一样的跨域问题。还有就是document.domain只适用于Cookie和iframe窗口,LocalStorage和IndexDB没法经过这种方法跨域。

5、window.name跨域

        window对象有一个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的全部页面都是共享一个window.name的,每一个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的全部页面中的,并不会因新页面的载入而进行重置。注意,window.name的值只能是字符串的形式,且这个字符串的大小最大能允许2M左右甚至更大的容量,因浏览器而异,但通常是够用的。

        example 1:

  如今在一个浏览器的一个标签页里打开http://www.knightboy.cn/a.html页面,你经过location.href = http://baidu.com/b.html,在同一个浏览器标签页里打开了不一样域名下的页面。这时候这两个页面你可使用window.name来传递参数。由于window.name指的是浏览器窗口的名字,只要浏览器窗口相同,那么不管在哪一个页面里访问都是同样的。

        example 2:

  你的http://www.knightboy.cn/a.html页面里使用<iframe>调用另外一个http://baidu.com/b.html页面。这时候你想在a页面里获取b页面里的DOM,而后进行操做。结果会发现不能得到b中的DOM。一样会由于不一样源而报错,和上面提到的不一样之处就是两个页面的一级域名也不相同。这时候document.domain就解决不了了。

        浏览器窗口有window.name属性。这个属性的最大特色就是,不管是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页能够读取它。好比当在b页面里设定window.name="hello",你再返回到a页面,在a页面访问window.name,能够获得hello。这种方法的优势是,window.name容量很大,能够放置很是长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

<!--a.html-->
<DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>跨域</title>
    <script>
        function getData() {//iframe载入后执行该函数
            var iframe = document.getElementById("proxy");
            iframe.onload=function() {//a.html与iframe属于同源了,能够互相访问
                var data = iframe.contentWindow.name;//获取iframe里的window.name,也就是b.html页面给它设置的数据
                alert(data);
        }
         iframe.src="data.html";//这里的data.html为随便的一个页面,目的是,使得a.html能访问到iframe里的内容,也可设置成about:blank
    </script>    
<head>
<body>
    <iframe id="proxy" src="http://baidu.com/b.html" style="display:none" onload="getData()"></iframe>
</body>
</html>
<!--b.html-->
<script>
    window.name="this is some data you got from b.html";
<script>

6、window.postMessage方法跨域

   window.postMessage是一个安全的跨源通讯方法。通常状况下,当且仅当执行脚本的页面使用相同的协议(一般都是http)、相同的端口(http默认80,https默认443)和相同的host(两个页面的document.domain的值相同)时,才容许不一样页面上的脚本互相访问。window.postMessage提供了一个可控的机制来安全地绕过这一限制,当其在正确使用的状况下。window.postMessage解决的不是浏览器与服务器之间的交互,解决的是浏览器不一样窗口之间的通讯问题,能够作的就是同步两个网页,固然这两个网页须要属于同一个基础域名。

  example 1:

  在a页面打开了一个不一样源的b页面,你想要让a和b这两个页面互相通讯,例如,a要访问b的LocalStorage。又或者,a页面里的iframe的src是不一样源的b页面,你想要让a和b两个页面互相通讯,好比依旧是想经过a访问b的LocalStorage。

  此时的解决办法是:利用HTML5中新增的跨文档通讯API,这个API为window对象新增了一个window.postMessage方法,容许跨窗口通讯,不论这两个窗口是否同源。a就能够把它的Local Storage发给b,反之,依然可行。

  window.postMessage(message, targetOrigin, [transfer])三个参数分别表示为:

  • message是向目标窗口发送的数据;
  • targetOrigin属性来指定哪些窗口能收到消息事件,其值能够是字符串“*”(表示无限制)或者一个URI(或者说是发送消息的目标域名);
  • transfer可选参数,是一串和message同时传递的Transferable对象,这些对象的全部权将被转移给消息的接收方,而发送一方将再也不保有全部权。

  另外就是,消息的接收方必须有监听事件,不然发送消息时就会报错。

  window.addEventListener("message", onmessage); onmessage接收到的message事件包含三个属性:

  data: 从其余window中传递过来的数据。

  origin: 调用postMessage时消息发送窗口的origin。这个origin不能保证是该窗口的当前或将来origin,由于postMessage被调用后可能被导航到不一样的位置。

  source: 对发送消息的窗口对象的引用;您可使用此来在具备不一样origin的两个窗口间创建双向通讯。

//在a页面执行
var
popUp = window.open('http://localhost:3000', 'title'); popUp.postMessage('Hello World!', 'http://localhost:3000');

  同时在http://localhost:3000的页面里监听message事件:

window.onload = function() {
    window.addEventListener('message', onmessage);
}
function onmessage(event) {
    if(event.origin == 'http://localhost:8080') {//"发送方a的域名"
        console.log(event.data);//"Hello World!"
    } else {
      console.log(event.data);//"Hello World!"
}
}

咱们来看另一个例子:

//发送端代码
var domain = "https://localhost",
    index = 1,
    target = window.open(`${domain}/postmesssage-target.html`);
function send()  {
    setInterval(() => {
        target.postMessage(`第${index++}次数据发送`, domain);
    }, 1000);
}
window.addEventListener("message", (e)=>{
    if(e.data === 'ok')
        send();
    else
        console.log(e.data);
});

//接收端代码
<head>
    <script>
        opener.postMessage("ok", opener.domain);
    </script>
</head>
<body>
<p id = "test"></p>
<script>
    var test = document.querySelector("#test");
    window.addEventListener("message", (e)=>{
        if(e.origin !== "http://localhost") {
            return void 0;
        }
        test.innerText = e.data;
    });
</script> 
</body>

  上面页面中,接受页面已经加载了,这时发送一个消息给发送端,发送端再开始向接收端发送数据。

7、片断识别符实现跨域

  片断识别符就是指URL的#号后面的部分。好比,http://example.com/x.html#fragment的#fragment。若是只是改变片断标识符,页面不会重复刷新。父窗口和iframe的子窗口之间的通信或者是window.open打开的子窗口之间的通信。

  父窗口能够把信息,写入子窗口的片断标识符。

var src= originURL + '#' + data;
document.getElementById('myIFrame').src = src;

  子窗口经过监听hashchange事件获得通知。

window.onhashchange = checkMessage;
function checkMessage() {
    var message = window.location.hash;
    //...  
}

  一样,子窗口也能够改变父窗口的片断标识符。

parent.location.href = target + '#' + hash;

  总之,父窗口改变子窗口的url的#号后面的部分,后者把要传递的的参数写在#后面,子窗口监听window.onhashchange事件,获得通知,读取window.location.hash解析出有用的数据。同时子窗口也能够向父窗口传递数据。

 

参考:

跨域同源政策及其规避方法

跨域解决方案大全

跨域问题汇总

相关文章
相关标签/搜索