浏览器的同源策略和跨域解决方法
同源的定义
根据MDN同源的定义:如果两个URL的protocol、port(如果有指定的话)和host都相同的话,则这两个URL是同源。
下表给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html | 同源 | 只有路径不同 |
https://store.company.com/secure.html | 失败 | 协议(protocol)不同 |
http://store.company.com:81/dir/etc.html | 失败 | 端口(port)不同 ( http:// 默认端口是80) |
http://news.company.com/dir/other.html | 失败 | 主机(host)不同 |
没有同源策略的危险(为什么需要同源策略)
同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
下面从DOM同源策略和XMLHttpRequest同源策略来举例说明:
如果没有DOM同源策略,也就是说不同域的iframe之间可以相互访问Dom结构,那么黑客可以这样进行攻击:
- 做一个假网站,里面用iframe嵌套一个银行网站http://mybank.com。
- 把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
- 这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的账户密码了。
如果没有XMLHttpRequest同源策略,那么黑客可以进行CSRF(跨站请求伪造)攻击:
- 用户登录了自己的银行页面http://mybank.com,http://mybank.com向用户的cookie中添加用户标识。
- 用户浏览了恶意页面http://evil.com,执行了页面中的恶意AJAX请求代码。
- http://evil.com向http://mybank.com发起AJAX请求,请求会默认把http://mybank.com对应cookie也同时发送过去。
- 银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。
- 而且由于Ajax在后台执行,用户无法感知这一过程。
其它例子可以参考:
同源策略的限制
如果非同源,共有三种行为受到限制。
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM 无法获得。
- AJAX 请求不能发送。
比如试图从不同源的iframe里面获取dom结构就会报错:
1 2 |
|
虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响:明明两个网页都是自己写的,但是属于不同的源,也就没有办法互相访问。
下面列举一些方法,介绍如何规避上面三种限制。前提是两个URL都是自己写的网页,否则无法使用。这也正是同源策略的作用所在:对于第三方的一个网页,你是无法超越上面三种限制的,这也就保证了安全。
常用跨域的解决方法–本地通信
修改document.domain
属性
Cookie是服务器写入浏览器的一小段信息,只有同源的网页才能共享。浏览器允许通过设置document.domain
共享Cookie。
但是,document.domain
只适用于“主域名相同,而子域名不同”的情况。这种方式非常适用于iframe跨域的情况。
举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要这两个网页同时设置相同的document.domain
,两个网页就可以共享Cookie。
注意,这种方法只适用于Cookie和iframe窗口,LocalStorage和IndexDB无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。
借助window.name
、location.hash
这两种方法可以说是一种“破解”。
以window.name
为例。浏览器窗口有window.name
属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
使用window.postMessage
window.name
和location.hash
是“破解”方法,window.postMessage
则具有官方背景。HTML5为了解决跨文档通信(Cross-document messaging)问题,引入了这个全新的API window.postMessage
。
这个API为window
对象新增了一个window.postMessage
方法,允许跨窗口通信,不论这两个窗口是否同源。
举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage
方法就可以了。
1 2 |
|
postMessage
方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。
子窗口向父窗口发送消息的写法类似。
1
|
|
父窗口和子窗口都可以通过message事件,监听对方的消息。
1 2 3 |
|
message事件的事件对象event,提供以下三个属性。
event.source
:发送消息的窗口event.origin
: 消息发向的网址event.data
: 消息内容
注:通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。
常用跨域的解决方法–AJAX请求限制
后面几种方法都是为了解决不同源网页之间的AJAX请求问题。
服务器代理
浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。
JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个\