浏览器跨域问题解决办法

前言

之前在一个项目中,调用了别的部门的接口,但是由于存在跨域问题,只能进行简单的本地数据模拟调试,然后再把资源给对方进行联调。这样的方法肯定不是最佳方案,正好最近看到一篇文章,说到的就是跨域问题的解决办法,所以进行了学习和整理。


同源政策

首先说说同源政策,最初,它的含义是指,A网页设置的Cookie,B网页不能打开,除非这两个网页“同源”。所谓“同源”,就是三个相同:

  1. 协议相同
  2. 域名相同
  3. 端口相同

同源政策的目的就是为了防止信息泄露。

如果非同源,共有三种行为会受到限制:

  1. Cookie、LocalStorage和IndexDB无法读取;
  2. DOM无法获得;
  3. Ajax请求不能发送。

下来说说如何针对上面三种情况进行跨域。


Cookie是服务器写入浏览器的一小段信息,只有同源的网页才能共享,但是,两个页面一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享Cookie。

举例来说,A网页是http://hr.163.com/a.html
B网页是http://study.163.com/b.html
主要设置相同的document.domain,两个网页就可以共享Cookie。

我们在两个页面都设置:

document.domain = '163.com';
  • 1
  • 1

然后在a网页设置cookie:

document.cookie = "test1=hello";
  • 1
  • 1

就可以在b网页读到这个cookie了:

var allCookie = document.cookie;
  • 1
  • 1

我们也可以在服务器进行配置,指定Cookie的所属域名为一级域名,这样,二级三级域名不用做任何设置,都可以读取这个cookie:

Set-Cookie: key=value; domain=.163.com; path=/
  • 1
  • 1

iframe

如果两个网页不同源,就无法拿到对方的DOM,典型的例子就是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。

父窗口获取子窗口:

document.getElementById("myIFrame").contentWindow.document
  • 1
  • 1

子窗口获取父窗口:

window.parent.document.body
  • 1
  • 1

如果两个窗口一级域名相同,只是二级域名不同,那么设置document.domain即可

如果是完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。

  1. 片段标示符
  2. window.name
  3. 跨文档通信API

片段标示符

片段标示符是URL的#后面的部分,比如
http://163.com/a.html#fragment的#fragment,如果只是改变片段标示符,页面不会重新刷新。

父窗口可以把信息,写入子窗口的片段标示符:

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

子窗口可以通过监听hashchange事件得到通知:

window.onhashchange = checkMessage;

function checkMessage() {
  var message = window.location.hash;
  // ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

同样的,子窗口也可以改变父窗口的片段标示符:

parent.location.href= target + "#" + hash;
  • 1
  • 1

window.name

浏览器窗口有window.name属性,这个属性最大的特点是,无论是否同源,只要在同一个窗口里,前一个页面设置了这个属性,后一个网页可以读取它。

父窗口先打开一个子窗口,载入一个不同源的页面,该网页将信息写入window.name属性:

window.name = data;
  • 1
  • 1

子窗口跳回一个与主窗口同域的网址:

location = 'http://parent.url.com/xx.html';
  • 1
  • 1

然后,主窗口就可以读取子窗口的window.name了:

var data = document.getElementById('myFrame').contentWindow.name;
  • 1
  • 1

优点是window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性值的变化,影响页面性能。

window.postMessage

HTML5的全新API:跨文档通信API。

父窗口
http://aaa.com
向子窗口http://bbb.com
发消息,调用postMessage方法就可以了:

var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
  • 1
  • 2
  • 1
  • 2

posetMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即”协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。

子窗口向父窗口发送消息的写法类似:

window.opener.postMessage('Nice to see you', 'http://aaa.com');
  • 1
  • 1

父窗口和子窗口都可以通过message事件,监听对方的消息:

window.addEventListener('message', function(e) {
  console.log(e.data);
},false);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

message事件的事件对象event,提供以下三个属性:

  1. event.source:发送信息的窗口
  2. event.origin:信息发向的网址
  3. event.data:信息内容

下面可以看看几个例子:

子窗口通过event.source属性引用父窗口,然后发送信息

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  event.source.postMessage('Nice to see you!', '*');
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

event.origin属性可以过滤不是发送本窗口的信息

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  if (event.origin !== 'http://aaa.com') return;
  if (event.data === 'Hello World') {
      event.source.postMessage('Hello', event.origin);
  } else {
    console.log(event.data);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

AJAX

ajax的跨域需求很常见的。现在有三种方法来解决:

  1. JSONP
  2. WebSocket
  3. CORS

JSONP

JSONP是服务器与客户端跨域通信的常用方法,最大的特点是简单适用,老式浏览器全部支持,服务器改造非常小。

它的思想是,页面通过添加一个<script>元素,向服务器请求JSON数据,服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

首先,网页动态插入<script>元素,由它向跨源网址发出请求:

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

服务器收到这个请求后,会将数据放在回调函数的参数位置返回:

foo({
  "ip": "8.8.8.8"
});
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

JSONP**只能发GET请求**,这点需要注意。

WebSocket

WebSocket是一种通信协议,使用ws://(非加密)
和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨域通信。

下面举例子,浏览器发出的websocket请求头:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

其中Origin字段,表示该请求的请求源,服务器可以判断这个字段,判断是否允许本次通信,如果同意,做出反应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

CORS

CORS可以允许任何类型的请求,是W3C的新标准。
首先,在请求头信息中添加Origin字段:

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

如果不在允许范围内,也会返回正常的HTTP回应,但是**没有**Access-Control-Allow-Origin字段,浏览器发现后,就知道出错了,从而抛出错误,被XMLHttpRequest的onerror回调函数捕获。

根据详细的字段配置,可以看看这篇文章
,讲的非常详细。

阅读更多
换一批

没有更多推荐了,返回首页