viviier

浏览器的同源策略和跨域方法

浏览器的同源策略和跨域方法

同源策略

简介

A网页设置的Cookie,B网页不能打开,除非这两个页面同源

所谓同源是指三个相同:

  • 协议相同
  • 域名相同
  • 端口相同

http://www.example.com/dir/page.html
协议(http://) 域名(www.example.com) 端口(80,默认可省略)

这个地址的同源情况如下:

同源策略的目的,是为了保护用户的信息安全,防止恶意的网站窃取数据。如果非同源,会受到三种行为限制:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM无法获得
  • Ajax无法发送

跨域方法

简介

在规避同源策略上,我们有很多种方法:

  • document.domain
  • window.name
  • window.postMessage
  • JSONP
  • WebSocket
  • CORS

比如用来处理一级域名二级域名不同的document.domain,以及更进一步可以读取LocalStorage的window.postMessage。在这里我们只介绍关于Ajax跨域的几个方法:

  • ……
  • JSONP
  • WebSocket
  • CORS

JSONP

jSONP的基本思路是,在网页动态添加一个script,向服务器请求JSON数据,服务器收到数据后,将数据放在指定名字的回调函数内进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function addScript(src) {
let script = document.createElement('script')
script.setAttribute('type', 'text/javascript')
script.src = src
document.body.appendChild(script)
}
window.onload = function(){
addScript('http://abc.com/ip?callback=foo')
}
function foo(data) {
console.log(data.ip)
}

服务器端可以这样写:

1
2
3
4
5
6
7
8
<?php
//服务端返回JSON数据
$arr=array('ip'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5);
$result=json_encode((object)$arr);
//动态执行回调函数
$callback=$_GET['callback'];
echo $callback."($result)";

上面代码通过动态添加script元素,向服务器http://abc.com发出请求,在请求的查询字符串中用callback来指定约定好的回调函数处理名字,服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。由于script元素请求的脚本直接作为代码运行,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。

WebSocket

WebSocket是一种通信协议,该协议不实行同源策略,只要服务器支持,就可以通过它进行跨域通信。

WebSocket的请求头信息如下:

1
2
3
4
5
6
7
8
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

上面代码中的Origin表示该请求的请求源,即发自哪个域名。服务器可以根据Origin判断是否许可本次通信,如果该域名在白名单内,就可以做出如下回应:

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

CORS

CORS允许浏览器向跨源服务器发出XMLHttpRequest请求,需要浏览器和服务器同时支持。实现这个功能只需由服务器发送一个响应标头即可。

假设我们页面或者应用已在http://www.test1.com上了,而我们打算从 http://www.test2.com请求提取数据。一般情况下,如果我们直接使用 AJAX 来请求将会失败,浏览器也会返回“源不匹配”的错误,”跨域”也就以此由来。利用CORShttp://www.test2.com只需添加一个标头,就可以允许来自http://www.test1.com的请求。

下面是PHP中的hander() 设置,“*”号表示允许任何域向我们的服务端提交请求:

1
header('Access-Control-Allow-Origin: *')

也可以设置指定的域名,如域名http://www.test2.com,那么就允许来自这个域名的请求:

1
header('Access-Control-Allow-Origin: http://www.test2.com')

当前我设置的header为“*”,任意一个请求过来之后服务端我们都可以进行响应和处理。

另外CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials为true。

1
Access-Control-Allow-Credentials: true

另一方面需要在AJAX请求中打开withCredentials属性:

1
2
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

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

相比JSONP,CORS更加强大,它支持所有类型的HTTP请求,而JSONP只支持GET。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

附录

参考文章

------本文结束感谢阅读------