跨域请求

同源策略与跨域请求

同源策略(Same Origin Policy),即URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。浏览器的同源策略,限制了来自不同源的 document 或脚本,对当前 document 读取或设置某些属性,即从一个域上加载的脚本不允许访问另外一个域的文档属性。比如一个恶意网站的页面通过iframe嵌入了银行的登录页面(二者不同源),如果没有同源限制,恶意网页上的JavaScript脚本就可以在用户登录银行的时候获取用户名和密码。同时,同源策略还能限制我们随意请求资源,避免大量的恶意请求。

常用的跨域请求方式分为客户端跨域、服务端跨域与CORS,服务端跨域即使用类似代理的方式,把访问其它域的请求替换为本域的请求,本域的请求是服务器端的动态脚本负责转发实际的请求。目前主流的浏览器皆支持CORS协议,其会在进行以下几种典型的跨域请求时被触发:

  • 不同的域名,譬如example.com的网页请求api.com
  • 不同的子域名,譬如example.com请求api.example.com
  • 不同的端口,譬如example.com请求example.com:3001
  • 不同的协议,譬如 https://example.com 请求 http://example.com

客户端跨域

表单POST跨域提交

JSONP

JSONP是较为常用的一种跨域方式,不受到浏览器兼容性的限制,但是因为它只能以GET动词进行请求,这样就破坏了标准的REST风格,比较丑陋。JSONP本质上是利用<script>标签的跨域能力实现跨域数据的访问,请求动态生成的JavaScript脚本同时带一个callback函数名作为参数。其中callback函数本地文档的JavaScript函数,服务器端动态生成的脚本会产生数据,并在代码中以产生的数据为参数调用callback函数。当这段脚本加载到本地文档时,callback函数就被调用。(1)浏览器端构造请求地址

function resolveJson(result) {
  console.log(result.name);
}
var jsonpScript = document.createElement("script");
jsonpScript.type = "text/javascript";
jsonpScript.src = "http://www.qiute.com?callbackName=resolveJson";
document.getElementsByTagName("head")[0].appendChild(jsonpScript);

标准的Script标签的请求地址为:请求资源的地址+获取函数的字段名+回调函数名称,这里的获取函数的字段名是需要和服务端提前约定好,譬如jQuery中默认的获取函数名就是callback。而resolveJson是我们默认注册的回调函数,注意,该函数名需要全局唯一,该函数接收服务端返回的数据作为参数,而函数内容就是对于该参数的处理。(2)服务端构造返回值在接受到浏览器端script的请求之后,从urlquerycallbackName获取到回调函数的名字,例子中是resolveJson。然后动态生成一段javascript片段去给这个函数传入参数执行这个函数。比如:

resolveJson({ name: "qiutc" });

(3)客户端以脚本方式执行服务端返回值服务端返回这个script之后,浏览器端获取到script资源,然后会立即执行这个javascript,也就是上面那个片段。这样就能根据之前写好的回调函数处理这些数据了。

## postMessage

window.postMessage是一个用于安全的使用跨源通信的方法。通常,不同页面上的脚本当且仅当执行它们的页面所处的位置使用相同的协议(通常都是http)、相同的端口(http默认使用80端口)和相同的主机(两个页面的document.domain的值相同)只在这种情况下被允许互相访问。而window.postMessage提供了一个受控的机制来安全地绕过这一限制。其函数原型如下:

windowObj.postMessage(message, targetOrigin);
  • windowObj:接受消息的Window对象。
  • message:在最新的浏览器中可以是对象。
  • targetOrigin:目标的源,* 表示任意。

调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 *。需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。上面所说的向其他window对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。

//在主页面中获取子页面的句柄
var iframe = document.getElementById("iframe");
var iframeWindow = iframe.contentWindow;
//向子页面发送消息
iframeWindow.postMessage("I'm message from main page.");
//在子页面中监听获取消息
window.onmessage = function (e) {
  e = e || event;
  console.log(e.data);
};

服务端跨域

上一页