浏览器同源策略
浏览器同源策略
什么试浏览器的同源策略
同源策略(Same origin policy)是⼀种约定,它是浏览器最核⼼也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的⼀种实现。
它的核⼼就在于它认为⾃任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运⾏在沙箱时,它们应该只被允许访问来⾃同⼀站点的资源,⽽不是那些来⾃其它站点可能怀有恶意的资源。
所谓同源是指:域名、协议、端⼝相同。
跨域的解决⽅法
CORS(跨域资源共享)
CORS(Cross-origin resource sharing,跨域资源共享)是⼀个 W3C 标准,定义了在必须访问跨域资源
时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使⽤⾃定义的 HTTP 头部让浏览器与服务器进⾏沟通,从⽽决定请求或响应是应该成功,还是应该失败。
CORS 需要浏览器和服务器同时⽀持。⽬前,所有浏览器都⽀持该功能,IE 浏览器不能低于 IE10。
整个 CORS 通信过程,都是浏览器⾃动完成,不需要⽤户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全⼀样。浏览器⼀旦发现 AJAX 请求跨源,就会⾃动添加⼀些附加的头信息,有时还会多出⼀次附加的请求,但⽤户不会有感觉。
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接⼝,就可以跨源通信。
浏览器将CORS请求分成两类:简单请求(simple request)和⾮简单请求(not-so-simple request)。
只要同时满⾜以下两⼤条件,就属于简单请求。
1. 请求⽅法是以下三种⽅法之⼀:
HEAD
GET
POST
1. HTTP的头信息不超出以下⼏种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain 凡是不同时满⾜上⾯两个条件,就属于⾮简单请求。
浏览器对这两种请求的处理,是不⼀样的。
简单请求
1.
2.
3. 没有这个头部或者有这个头部但源信息不匹配,浏览器就会驳回请求。正常情况下,浏览器会处理请求。注意,请求和响应都
不包含 cookie 信息。
4. 如果需要包含 cookie 信息,ajax 请求需要设置 xhr 的属性 withCredentials 为 true,服务器需要设置响应头部Access-Control-
Allow-Credentials: true。
⾮简单请求
浏览器在发送真正的请求之前,会先发送⼀个 Preflight 请求给服务器,这种请求使⽤ OPTIONS ⽅法,发送下列头部:
Origin:与简单的请求相同。
Access-Control-Request-Method: 请求⾃⾝使⽤的⽅法。
Access-Control-Request-Headers: (可选)⾃定义的头部信息,多个头部以逗号分隔。
例如:
Origin: www.laixiangran
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ
发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览器进⾏沟通:
Access-Control-Allow-Origin:与简单的请求相同。
Access-Control-Allow-Methods: 允许的⽅法,多个⽅法以逗号分隔。
Access-Control-Allow-Headers: 允许的头部,多个⽅法以逗号分隔。
Access-Control-Max-Age: 应该将这个 Preflight 请求缓存多长时间(以秒表⽰)。
例如:
Access-Control-Allow-Origin: www.laixiangran
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000
⼀旦服务器通过 Preflight 请求允许该请求之后,以后每次浏览器正常的 CORS 请求,就都跟简单请求⼀样了。
优点
CORS 通信与同源的 AJAX 通信没有差别,代码完全⼀样,容易维护。
⽀持所有类型的 HTTP 请求。
缺点
存在兼容性问题,特别是 IE10 以下的浏览器。
第⼀次发送⾮简单请求时会多⼀次请求。
JSONP 跨域
由于script标签不受浏览器同源策略的影响,允许跨域引⽤资源。因此可以通过动态创建 script 标签,然后利⽤ src 属性进⾏跨域,这也就是 JSONP 跨域的基本原理。
直接通过下⾯的例⼦来说明 JSONP 实现跨域的流程:
iframe参数传递
// 1. 定义⼀个回调函数 handleResponse ⽤来接收返回的数据
function handleResponse(data) {
console.log(data);
};
// 2. 动态创建⼀个 script 标签,并且告诉后端回调函数名叫 handleResponse
var body = ElementsByTagName('body')[0];
var script = Element('script');
script.src = 'www.laixiangran/json?callback=handleResponse';
body.appendChild(script);
// 3. 通过 script.src 请求 `www.laixiangran/json?callback=handleResponse`,
// 4. 后端能够识别这样的 URL 格式并处理该请求,然后返回 handleResponse({"name": "laixiangran"}) 给浏览器
// 5. 浏览器在接收到 handleResponse({"name": "laixiangran"}) 之后⽴即执⾏,也就是执⾏ handleResponse ⽅法,获得后端返回的数据,这样就完成⼀次跨域请求了。
优点
使⽤简便,没有兼容性问题,⽬前最流⾏的⼀种跨域⽅法。
缺点
只⽀持 GET 请求。
由于是从其它域中加载代码执⾏,因此如果其他域不安全,很可能会在响应中夹带⼀些恶意代码。
要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给 script 标签新增了⼀个 onerror 事件处理程序,但是存在兼容性问
题。
window.name 跨域
window 对象有个 name 属性,该属性有个特征:即在⼀个窗⼝(window)的⽣命周期内,窗⼝载⼊的所有的页⾯(不管是相同域的页⾯还是不同域的页⾯)都是共享⼀个window.name的,每个页⾯对window.name都有读写的权限,window.name是持久存在⼀个窗⼝载⼊过的所有页⾯中的,并不会因新页⾯的载⼊⽽进⾏重置。
通过下⾯的例⼦介绍如何通过 window.name 来跨域获取数据的。
<iframe src="laixiangran/b.html" id="myIframe" onload="test()" >
<script>
// 2. iframe载⼊ "laixiangran/b.html 页⾯后会执⾏该函数
function test() {
var iframe = ElementById('myIframe');
/
/ 重置 iframe 的 onload 事件程序,
// 此时经过后⾯代码重置 src 之后,
// www.laixiangran/a.html 页⾯与该 iframe 在同⼀个源了,可以相互访问了
var data = tWindow.name; // 4. 获取 iframe ⾥的 window.name
console.log(data); // hello world!
};
// 3. 重置⼀个与 www.laixiangran/a.html 页⾯同源的页⾯
iframe.src = 'www.laixiangran/c.html';
}
</script>
<script type="text/javascript">
// 1. 给当前的 window.name 设置⼀个 www.laixiangran/a.html 页⾯想要得到的数据值
window.name = "hello world!";
</script>
location.hash 跨域
location.hash ⽅式跨域,是⼦框架具有修改⽗框架 src 的 hash 值,通过这个属性进⾏传递数据,且更改 hash 值,页⾯不会刷新。但是传递的数据的字节数是有限的。
<iframe src="laixiangran/b.html" id="myIframe" onload="test()" >
<script>
// 2. iframe载⼊ "laixiangran/b.html 页⾯后会执⾏该函数
function test() {
// 3. 获取通过 laixiangran/b.html 页⾯设置 hash 值
var data = window.location.hash;
console.log(data);
}
</script>
<script type="text/javascript">
// 1. 设置⽗页⾯的 hash 值
parent.location.hash = "world";
</script>
postMessage 跨域
window.postMessage(message,targetOrigin) ⽅法是 HTML5 新引进的特性,可以使⽤它来向其它的 window 对象发送消息,⽆论这个window 对象是属于同源或不同源。这个应该就是以后解决 dom 跨域通⽤⽅法了。
调⽤ postMessage ⽅法的 window 对象是指要接收消息的那⼀个 window 对象,该⽅法的第⼀个参数 message 为要发送的消息,类型只能为字符串;第⼆个参数 targetOrigin ⽤来限定接收消息的那个 window 对象所在的域,如果不想限定域,可以使⽤通配符 *。
需要接收消息的 window 对象,可是通过监听⾃⾝的 message 事件来获取传过来的消息,消息内容储存在该事件对象的 data 属性中。
<iframe src="laixiangran/b.html" id="myIframe" onload="test()" >
<script>
// 1. iframe载⼊ "laixiangran/b.html 页⾯后会执⾏该函数
function test() {
// 2. 获取 laixiangran/b.html 页⾯的 window 对象,
// 然后通过 postMessage 向 laixiangran/b.html 页⾯发送消息
var iframe = ElementById('myIframe');
var win = tWindow;
win.postMessage('我是来⾃ www.laixiangran/a.html 页⾯的消息', '*');
}
</script>
<script type="text/javascript">
// 注册 message 事件⽤来接收消息
e = e || event; // 获取事件对象
console.log(e.data); // 通过 data 属性得到发送来的消息    }
</script>