Fork me on GitHub

跨域解决方案

跨域解决方案

跨域解决方案有:设置document.domain,使用带src标签,JSONP,navigation对象,CORS,window.postMessage,片段标识符,window.name,WebSocket

设置document.domain

  • 原理:相同主域名不同子域名下的页面,可以设置document.domain让它们同域
  • 限制:同域document提供的是页面间的互操作,需要载入iframe页面
1
2
3
4
5
6
7
8
9
10
// URL http://a.com/foo
var ifr = document.createElement('iframe');
ifr.src = 'http://b.a.com/bar';
ifr.onload = function(){
var ifrdoc = ifr.contentDocument || ifr.contentWindow.document;
ifrdoc.getElementsById("foo").innerHTML);
};

ifr.style.display = 'none';
document.body.appendChild(ifr);

需要设置iframe的domain,将 document.domain往上设置一级,这样即可操作DOM和Cookie

1
document.domain = 'a.com'

使用带src标签

  • 原理:所有具有src属性的HTML标签都是可以跨域的,包括<img>, <script>,<iframe>
  • 限制:只能用于GET方法

JSONP

利用script标签可以跨域这点,跨域获得的脚本包含一个客户端和服务器端约定好的回调函数,以及服务器端发送的数据。

jQuery实现

1
2
3
4
//URL具有callback参数时, jQuery将会把它解释为一个JSONP请求,创建一个<script>标签来完成该请求。
$.getJSON( "http://b.a.com/bar?callback=callback", function( data ){
// 处理跨域请求得到的数据
});

JS实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function loadJsonp(url,callback){
var script = document.createElement('script'),
rand = Math.random().toString().substring(2, 8),
functionName = "getJsonStr" + rand;

script.src = url + "?callback=" + functionName;

window[functionName] = function(data) {
if (callback) {
callback(data);
}
try {
delete window[functionName];
} catch (e) {
window[functionName] = undefined;
}
};

var head = document.getElementsByTagName('head')[0];
script.onload = function() {
script.onload = undefined;
head.removeChild(script);
};
script.onerror = function(e) {
console.error(e);
};

head.appendChild(script);
}

跨域资源共享(CORS)

  • 原理:服务器设置Access-Control-Allow-OriginHTTP响应头之后,浏览器将会允许跨域请求,H5推荐的跨域方式。
  • 限制:浏览器需要支持HTML5,可以支持POST,PUT等方法

跨域发送Cookie

将xhr的属性withCredentials设置为true后,即可携带目标域的Cookie

1
2
3
4
5
6
7
8
9
10
11
12
// 原生
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();
// jQ
$.ajax({
url: a_cross_domain_url,
xhrFields: {
withCredentials: true
}
});

还需要服务器端设置Access-Control-Allow-Credentials响应头为true,并且将Access-Control-Allow-Origin设置为请求对应的域名

既然Access-Control-Allow-Origin只允许单一域名, 服务器可能需要维护一个接受 Cookie 的 Origin 列表, 验证 Origin 请求头字段后直接将其设置为Access-Control-Allow-Origin的值。 (这一实践来自 Stackoverflow) 值得注意的是在 CORS 请求被重定向后 Origin 头字段会被置为 null。 此时可以选择从Referer头字段计算得到Origin。

preflight

对于非简单请求,CORS 机制跨域会首先进行 preflight(一个 OPTIONS 请求), 该请求成功后才会发送真正的请求。 这一设计旨在确保服务器对 CORS 标准知情,以保护不支持 CORS 的旧服务器。

preflight

window.postMessage

  • 原理:HTML5允许窗口之间发送消息
  • 限制:浏览器需要支持HTML5,获取窗口句柄后才能相互通信

这是一个安全的跨域通信方法,postMessage(message,targetOrigin)也是HTML5引入的特性。 可以给任何一个window发送消息,不论是否同源。第二个参数可以是*但如果你设置了一个URL但不相符,那么该事件不会被分发。

1
2
3
4
5
6
7
// 页面A,URL: http://a.com/foo
var win = window.open('http://b.com/bar');
win.postMessage('Hello, bar!', 'http://b.com');
// 页面B,URL: http://b.com/bar
window.addEventListener('message',function(event) {
console.log(event.data);
});

片段标识符

  • 原理:改变网页#后面的部分,被改变的网页可以通过监听onhashchange事件得到通知
  • 限制:URL具有长度的限制,只能传送字符串
1
2
3
4
5
6
7
// A窗口
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
// B窗口
window.onhashchange = function(){
var message = window.location.hash;
}

window.name

  • 原理:只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
  • 限制:必须监听子窗口window.name属性的变化,影响网页性能。

WebSocket

  • 原理:WebSocket使用ws、wss作为通信协议,改协议没有同源策略
  • 限制:需要浏览器支持

参考文献

JS高程(第三版)
Web开发中跨域的几种解决方案
浏览器同源策略及其规避方法
CORS 跨域发送 Cookie
CORS 跨域中的 preflight 请求

坚持原创技术分享,您的支持将鼓励我继续创作!