解决前端跨域方法

前言

为了保证用户信息的安全,防止恶意的网站窃取数据,浏览器制定了同源政策。同源指的是:域名相同、协议相同、端口相同。同源策略一定能够程度上保证了安全,有时候却造成了不方便,由此前端跨域问题就出现了。我们需要跨域获取数据和进行通信。

当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

跨域的方法

document.domain

页面可以改变自己的源,但是也有一些限制。只能赋值为当前的子域名和主域名

document.domain = "csdn.net"; // 成功,为当前主域名
docoment.domain = "blog.csdn.net"; //  成功,为当前子域名
document.domain = "taobao.com"; // 失败。不同域

利用document.domain进行跨域,必须属于同一个域名,而且所用协议和端口都要相同,否则无法通过document.domain来进行跨域。

例如有一个页面,它的地址是http://www.abc.com/one.html,在这个页面有一个iframe,它的src是http://abc.com/two.html,显示这个页面和iframe是不同的域,所以无法在当前页面通过js获取iframe的窗口信息。
这个时候document.domain 就派上用场了。

// 当前页面为 http://www.abc.com/one.html
<iframe id = "iframe" src="http://abc.com/two.html" onload = "test()">iframe>
<script type="text/javascript">
    document.domain = 'abc.com'; //设置成主域
    function test(){
        console.log(document.getElementById('iframe').contentWindow);//contentWindow 可取得iframe的 window 对象
    }
script>

通过这样的方式就能够进行不同子域下的两个页面的交互。

location.hash

通过location.hash跨域可以在两个完全不同域进行通信。缺点是url是有长度限制,传递的数据不能太长,而且数据会暴露在url中,安全性不高。
具体示例如下:
在a.com/one.html下嵌入一个iframe,其src指向b.com/two.html。
1. 当需要one.html向two.html传递信息时
    - one.html页面修改iframe的src为b.com/two.html#data。
    - two.html页面监听到url发生变化,触发相应的操作。

2. 当需要two.html向one.html传递信息时
    - two.html创建一个隐藏的iframe,src指向one.html在的域名a.com,加上传递的数据。
    - 隐藏的iframe监听到url发生变化(因为该iframe和a.com同域,所以可以修改同域的one.html的hash,达到传送数据目的。)       
    - one.html监听到url发生变化,触发相应的操作。

window.name

window.name不是一个普通的全局变量,它是当前窗口的名字。它即使在不同页面(甚至不同域名)重新加载,只要不被修改就都存在,支持的name值也很长(2M)。
首先需要三个页面:a.com/one.html(需要获取数据的页面),a.com/proxy.html(空白页,提供代理的功能),b.com/data.html(提供数据的页面)。

  1. 在one.html页面创建一个iframe,其src指向data.html。在data.html设置window.name的值。
  2. 在one.html监听iframe的onload事件,proxy.html和one.html在同一域下,所以可以相互通信。
<script>
    var state = 0;
    var iframe = document.createElement('iframe');
    var loadFn = function() {
        if (state === 1) {
            var data = iframe.contentWindow.name;
            console.log(data);
            // 获取数据后删除iframe
            iframe.contentWindow.document.write('');
            iframe.contentWindow.close();
        } else if (state === 0){
            state = 1;
            iframe.contentWindow.location = "http://a.com/proxy.html";
        }   
    };
    // 兼容IE
    if (iframe.attachEvent) {
        iframe.attachEvent('onload', loadFn);
    } else {
        iframe.onload = loadFn;
    }
    iframe.src="http://b.com/data.html";
    document.body.appendChild(iframe);
script>

实际就是一个iframe的src指向外域(b.com),在外域获取数据后转向本域,跨域的数据就从iframe的contentWindow.name转到了本域。

window.postMessage

HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。
具体使用otherWindow.postMessage(message, targetOrigin, [transfer]);
其中otherWindow 是其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。message 是要发送到其它window的数据, targetOrigin 是用来指定哪些窗口能接收到数据,但是注意必须添加具体URI。
窗口之间通信通过message 事件监听对方消息,其事件对象event提供以下三个属性。

  • event.source:发送消息的窗口。
  • event.origin: 消息发向的网址。
  • event.data:消息内容。

当a.com向b.com发送信息时,调用postMessage方法

var popup = window.open('http://b.com');
popup.postMessage('ni hao ya', 'http://b.com');

b.com监听message得到数据。

var onMessage = function(event) {
    var source = event.source;
    var origin = event.origin;
    var data = event.data;
    if (origin === 'http://a.com') {
        console.log(data); // ni hao ya
    }
}
if (window.addEventListener) {
     // 标准浏览器
    window.addEventListener('message', onMessage, false);
} else if (window.attachEvent) {
   // 兼容IE
    window.attachEvent('onmessage', onMessage);
}

jsonp

添加一个script标签,向服务器请求json数据,这种做法不受同源政策的影响,服务器收到请求后,将数据放在请求中指定名字的回调函数里传回来。

var script = document.createElement('script');
script.src = "http://www.baidu.com/s?callback=result";
document.body.appendChild(script);
function result(data) {
    console.log('the data is ' + data.name); // the data is js
}
// 服务器返回
result({name: 'js'});

实现简单快捷,对老式浏览器支持好,不需要XMLHttpRequest和ActiveX对象的支持,但是这种方法只能是GET请求。

CORS(跨域资源共享)

这种跨域方式关键在于服务器,当服务器检测到跨域请求时,就自动在请求头添加附加的头信息。

简单请求
某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”。若请求满足所有下述条件,则该请求可视为“简单请求”:
请求方法为GET、HEAD、POST,仅当Content-Typetext/plainmultipart/form-data
application/x-www-form-urlencoded时才属于简单请求。
www.foo.com访问www.bar.com中资源时候。请求头中有Origin字段为www.foo.com,
www.bar.com服务端只允许foo.com访问该资源,那么响应头中就有Access-Control-Allow-Origin:http://www.foo.com,如果是允许任何外域访问该资源,那么响应头的字段Access-Control-Allow-Origin: *

预检请求
预检请求要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
当请求方法为GET、HEAD、POST 之外的方法时,或者Content-Type 不属于text/plainmultipart/form-dataapplication/x-www-form-urlencoded 时,这个请求就需要发送预检请求。
先使用OPTIONS 方法发送预检请求,从服务器获取信息,该方法不会对服务器资源产生影响,当确认服务器允许该请求,才发送正式请求。

附带身份凭证的请求
一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位,例如设置withCredentials 为true。如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true,浏览器将不会把响应内容返回给请求的发送者。

参考

浏览器同源政策及其规避方法
设置document.domain实现js跨域注意点
window.name实现的跨域数据传输
window.postMessage
前端跨域问题及解决方案
前端跨域整理
HTTP 访问控制(CORS)

你可能感兴趣的:(跨域)