概述
含义
“同源”指的是三个相同:
- 协议相同
- 域名相同
- 端口相同
1 | http://www.example.com/dir/page.html |
目的
保护用户信息,防止恶意的网站窃取数据
举个例子:用户在一家银行网站登录后,再进入其他网站,其他网站可以拿到之前银行网站存下来的Cookie,这会发生什么?
如果Cookie里面包含隐私(比如余额),那这些信息就会被泄漏。还有更可怕的是,Cookies往往被用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器还规定提交表单不受通源策略的限制。
因此“通源策略“是必须的,否则Cookies可以共享,互联网毫无安全可言。
限制范围
随着互联网的发展,”同源策略”越来越严格。目前,如果非同源,有下面三种限制:
- Cookies、 LocalStorage 和 IndexDB无法读取
- DOM无法获得
- AJAX请求无法发送
虽然这些限制是必要的,但是有时,合理的用途也会收到影响
Cookie
Cookies是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是两个网页,一级域名相同,二级域名不同,浏览器允许通过设置document.domain,两个网页就可以共享Cookie
举个🌰
网页A: http://w1.example.com/a.html
网页B: http://w2.example.com/b.html
那么只要设置相同的document.domain,那么两个网页就可以共享Cookie
1 | document.domain = 'example.com'; |
现在网页A通过脚本设置一个Cookie
1 | document.cookie = 'hello test'; |
B网页就可以读到这个Cookie
1 | const allCookie = document.cookie |
这个方法只适用于Cookie和iframe窗口,LocalStorage 和 IndexDB无法通过这种方法规避同源策略,而要使用下面介绍的PostMessage API
服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com
1 | Set-Cookie: key=value; domain=.example.com; path=/ |
这样的话,二级域名和三级域名不用设置任何,都可以读到Cookie
iframe
如果两个网页不同源,就无法拿到对方的DOM, 最典型的就是iframe和window.open打开的窗口,他们与父窗口都无法通信
比如,父窗口允许下面的命令,如果iframe窗口不是同源,就会报错
1 | document.getElementById('myIframe').contentWindow.document |
反之亦然,子窗口获取主窗口的DOM也会报错
1 | window.parent.document.body |
如果两个窗口一级域名相同,只是二级域名不同,那就可以使用上面的方法,设置doment.domain来规避同源策略
对于完全不同源的网站,目前有3种方法来解决跨域窗口的通信问题
- 片段识别符 (Fragment Indentifier)
- window.name
- 跨文档通信API (Cross-document messaging)
片段识别符
片段识别符指的是URL的#后面的那一部分,比如http://example.com/x.html#fragment的 #fragment 如果只是改变片段标识符,不会刷新页面
父窗口可以把信息写入子窗口的片段标识符
1 | var src = orginUrl + '#' + data; |
子窗口通过监听hashChange事件可以得到通知
1 | window.onhashchange = checkMessage; |
同样的,子窗口也可以改变父窗口的片段标识符
1 | parent.location.href = targe + '#' + hash; |
window.name
浏览器窗口有一个window.name属性,这个属性最大的特点,无论是否同源,只要在同一个窗口里面,前一个网页设置了它,后一个网页就可以读取它
父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性
1 | window.name = data; |
接着子窗口跳回一个与主窗口同域的网址
1 | location = 'http://parent.url.com/xxx.html'; |
然后主窗口就可以读取子窗口的window.name
1 | var data = document.getElementById('myFrame').contentWindow.name; |
这种方法的优点是,window.name容量很大,可以放置非常长的字符。缺点是必须监听子窗口的window.name属性变化,影响网页性能
window.postMessage
上面两种方法都属于破解,HTML为了解决这个问题,引入一个全新的API: 跨文档通信API(Cross-document messaging)
这个API为window新增了一个window.postMessage方法,允许跨窗口通信,不论是否同源
举个🌰
父窗口http://aaa.com 向子窗口http://bbb.com发消息,调用postMessage方法就可以
1 | var popup = window.open('http://bbb.com', 'title'); |
postMessage方法的第一个参数是具体的信息内容,第二个参数是接受信息的窗口的源(origin),即“协议 + 域名 + 端口”。也可以设为“*”,表示不限制域名,向所有窗口发送
子窗口向父窗口发送消息的写法类似:
1 | window.opener.postMessage('nice to see you', 'http://aaa.com'); |
子窗口和父窗口都可以message事件,监听对方的消息
1 | window.addEventListener('message', function(e) { |
message事件的事件对象event, 提供以下三个属性:
- event.source 发送消息的窗口
- event.origin 消息发向的网址
- event.data 消息内容
下面的例子就是,子窗口通过event.source属性引用父窗口,然后发送消息
1 | window.addEventListener('message', receiveMessage); |
event.origin属性可以过滤是不是发送给本窗口的消息
1 | window.addEventListener('message', receiveMessage); |
LocalStorage
通过window.postMessage, 读取其他窗口的LocalStorage也成为了可能
下面的例子就是,主窗口写入iframe子窗口的LocalStorage
1 | window.onMessage = function(e) { |
上面的代码,子窗口将父窗口发送过来的消息写入localStorage
父窗口发送消息的代码如下:
1 | var win = document.getElementById('iframe')[0].contentWindow; |
加强版的子窗口接受消息代码:
1 | window.onmessage = function(e) { |
加强版的父窗口发送消息代码:
1 | var win = document.getElementById('iframe')[0].contentWindow; |
AJAX
同源策略规定,AJAX请求只能发送给同源的网址,否则就会报错
除了假设服务器代理(浏览器请求同源服务器,再由后者请求外部服务器),有3种方法规避这个限制
- JSONP
- WebSocket
- CORS
JSONP
JOSNP是服务器和客户端跨源通信的常用方法,最大的特点是简单适用,老式浏览器全部支持,服务器改造非常小
它的基本思想是:网页通过添加一个script元素,向服务器请求JSON数据,这种方法不受同源策略限制;服务器收到请求后,将数据放到一个指定名字的回调函数里面传回来
首先,网页动态插入script元素,由它向跨源网址发起请求
1 | function addScriptTag(src) { |
上面代码,动态添加一个script元素,向服务器example.com发出请求,注意请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必须的
服务器收到请求后,会将数据放到回调函数的入参位置返回
1 | foo({ |
由于script元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数,JSON数据被视为javascript对象,而不是字符串,因此避免了JSON.parse的步骤
WebSocket
webSocket是一种通信协议,作为ws://(非加密),和wss://(加密)作为协议前缀。该协议不实行同源策略,只要服务器支持,就可以通过它进行跨源通信。
下面是一个例子,浏览器发出的WebSocket请求的头信息
1 | GET /chat HTTP/1.1 |
上面的代码中,有一个Origin,表示该请求的请求源(orgin),即发自哪个域名
就是因为Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会作出以下回应:
1 | HTTP/1.1 101 Switching Protocols |
CORS
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,它是跨源AJAX请求的根本解决方法。相比,JSONP只能发GET请求,CORS允许任何类型的请求。