JavaScript高级语法之跨域(postMessage,CORS)

目录

1 postMessage概述

1.1 iframe与主页面的通信

2 CORS

2.1 发送请求,“预检”机制

2.2 收到响应


1 postMessage概述

所谓“同域限制”指的是,出于安全考虑,浏览器只允许脚本与同样协议、同样端口、同样域名的地址进行通信。比如,www1.example.com页面上面的脚本,只能与该域名(相同协议、相同端口)进行通信,如果与www2.example.com通信,浏览器就会报错(不过可以设置两者的document.domain为相同的值)。这是为了防止恶意脚本将用户信息发往第三方网站。

window.postMessage方法就是用来在某种程度上,绕过同域限制,实现不同域名的窗口(包括iframe窗口)之间的通信。它的格式如下。

targetWindow.postMessage(message, targetURL[, transferObject]);

上面代码的targetWindow是指向目标窗口的变量,message是要发送的信息,targetURL是指定目标窗口的网址,不符合该网址就不发送信息,transferObject则是跟随信息一起发送的Transferable对象。

下面是一个postMessage方法的实例。假定当前网页弹出一个新窗口。

var popup = window.open(...popup details...);

popup.postMessage("Hello World!", "http://example.org");

上面代码的postMessage方法的第一个参数是实际发送的信息,第二个参数是指定发送对象的域名必须是example.org。如果对方窗口不是这个域名,信息不会发送出去。

然后,在当前网页上监听message事件。

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event) {
    if (event.origin !== "http://example.org")
    return;

    if (event.data == 'Hello World') {
      event.source.postMessage('Hello', event.origin);
    } else {
      console.log(event.data);
    }

}

上面代码指定message事件的回调函数为receiveMessage,一旦收到其他窗口发来的信息,receiveMessage函数就会被调用。receiveMessage函数接受一个event事件对象作为参数,该对象的origin属性表示信息的来源网址,如果该网址不符合要求,就立刻返回,不再进行下一步处理。event.data属性则包含了实际发送过来的信息,event.source属性,指向当前网页发送信息的窗口对象

最后,在popup窗口中部署下面的代码。

// popup窗口

function receiveMessage(event) {
  event.source.postMessage("Nice to see you!", "*");
}

window.addEventListener("message", receiveMessage, false);

上面代码有几个地方需要注意:

首先,receiveMessage函数里面没有过滤信息的来源,任意网址发来的信息都会被处理。其次,postMessage方法中指定的目标窗口的网址是一个星号,表示该信息可以向任意网址发送。通常来说,这两种做法是不推荐的,因为不够安全,可能会被恶意利用。

所有浏览器都支持这个方法,但是IE 8和IE 9只允许postMessage方法与iFrame窗口通信,不能与新窗口通信。IE 10允许与新窗口通信,但是只能使用IE特有的MessageChannel对象。

1.1 iframe与主页面的通信

iframe中的网页,如果与主页面来自同一个域,通过设置document.domain属性,可以使用postMessage方法实现双向通信

下面是一个LocalStorage的例子。LocalStorage只能用同一个域名的网页读写,但是如果iframe是主页面的子域名,主页面就可以通过postMessage方法,读写iframe网页设置的LocalStorage数据。

iframe页面的代码如下。

document.domain = "domain.com";
window.onmessage = function(e) {
  if (e.origin !== "http://domain.com") {
    return;
  }
  var payload = JSON.parse(e.data);
  localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

主页面的代码如下。

window.onload = function() {
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = {
       name: "Jack"
    };
    win.postMessage(JSON.stringify({key: 'storage', data: obj}), "*");
};

上面的代码已经可以实现,主页面向iframe传入数据。如果还想读取或删除数据,可以进一步加强代码。

加强版的iframe代码如下。

document.domain = "domain.com";
window.onmessage = function(e) {
    if (e.origin !== "http://domain.com") {
        return;
    }
    var payload = JSON.parse(e.data);
    switch(payload.method) {
        case 'set':
            localStorage.setItem(payload.key, JSON.stringify(payload.data));
            break;
        case 'get':
            var parent = window.parent;
            var data = localStorage.getItem(payload.key);
            parent.postMessage(data, "*");
            break;
        case 'remove':
            localStorage.removeItem(payload.key);
            break;
    }
};

加强版的主页面代码如下。

window.onload = function() {
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = {
       name: "Jack"
    };
    // 存入对象
    win.postMessage(JSON.stringify({key: 'storage', method: "set", data: obj}), "*");
    // 读取以前存取的对象
    win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
    window.onmessage = function(e) {
        if (e.origin != "http://sub.domain.com") {
            return;
        }
        // 下面会输出"Jack"
        console.log(JSON.parse(e.data).name);
    };
};

2 CORS

CORS的全称是“跨域资源共享”(Cross-origin resource sharing),它提出一种方法,允许JavaScript代码向另一个域名发出XMLHttpRequests请求,从而克服了传统上Ajax只能在同一个域名下使用的限制(same origin security policy)。

所有主流浏览器都支持该方法,不过IE8和IE9的该方法不是部署在XMLHttpRequest对象,而是部署在XDomainRequest对象。检查浏览器是否支持的代码如下:

var request = new XMLHttpRequest();

if("withCredentials" in request) {
  // 发出跨域请求
}

2.1 发送请求,“预检”机制

CORS的原理其实很简单,就是增加一条HTTP头信息的查询,询问服务器端,当前请求的域名是否在许可名单之中,以及可以使用哪些HTTP动词。如果得到肯定的答复,就发出XMLHttpRequest请求。这种机制叫做“预检”(preflight)

“预检”的专用HTTP头信息是Origin。假定用户正在浏览来自www.example.com的网页,该网页需要向Google请求数据,这时浏览器会向该域名询问是否同意跨域请求,发出的HTTP头信息如下:

OPTIONS /resources/post-here/ HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

上面的HTTP请求,它的动词是OPTIONS,表示这是一个“预检”请求。除了提供浏览器信息,里面关键的一行是Origin头信息。

Origin: http://www.example.com

这行HTTP头信息表示,请求来自www.example.com。服务端如果同意,就返回一个Access-Control-Allow-Origin头信息。

预检请求中,浏览器还告诉服务器,实际发出请求,将使用HTTP动词POST,以及一个自定义的头信息X-PINGOTHER。

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

2.2 收到响应

服务器收到预检请求之后,做出了回应。

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP回应里面,关键的是Access-Control-Allow-Origin头信息。这表示服务器同意www.example.com的跨域请求。

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

如果不同意,服务器端会返回一个错误。

注意:如果服务器端对所有网站都开放,可以返回一个星号(*)通配符。

Access-Control-Allow-Origin: *

服务器还告诉浏览器,允许的HTTP动词是POST、GET、OPTIONS,也允许自定义的头信息X-PINGOTHER,

Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000

如果服务器通过了预检请求,则以后每次浏览器正常的HTTP请求,都会有一个origin头信息;服务器的回应,也都会有一个Access-Control-Allow-Origin头信息。Access-Control-Max-Age头信息表示,允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

由于整个过程都是浏览器自动后台完成,不用用户参与,所以对于开发者来说,使用Ajax跨域请求与同域请求没有区别,代码完全一样。但是,这需要服务器的支持,所以在使用CORS之前,要查看一下所请求的网站是否支持。

CORS机制默认不发送cookie和HTTP认证信息,除非在Ajax请求中打开withCredentials属性。

var request = new XMLHttpRequest();
request.withCredentials = true;

同时,服务器返回HTTP头信息时,也必须打开Access-Control-Allow-Credentials选项。否则,浏览器会忽略服务器返回的回应。

Access-Control-Allow-Credentials: true

需要注意的是,此时Access-Control-Allow-Origin不能指定为星号,必须指定明确的、与请求网页一致的域名。同时,cookie依然遵循同源政策,只有用服务器域名(前例是www.google.com)设置的cookie才会上传,其他域名下的cookie并不会上传,且网页代码中的document.cookie也无法读取www.google.com域名下的cookie。

CORS可以支持所有类型的HTTP请求。在发生错误的情况下,CORS可以得到更详细的错误信息,部署更有针对性的错误处理代码。

猜你喜欢

转载自blog.csdn.net/u012060033/article/details/89965798