浏览器的同源策略
本文最后更新于:8 个月前
介绍浏览器的同源策略、以及如何解决同源策略带来的跨域问题
浏览器的同源策略
一、同源策略
所有浏览器都遵循的一个策略。A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”
定义
- 相同的协议(protocol),http与https不同
- 相同的域名,www.baidu.com 与 baidu.com不同
- 相同的端口号(有些浏览器不要求,IE<9),80与8080不同
限制范围
无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
用户登录某个站点,站点后端服务器验证账号密码正确之后,会返回Cookie、Token 或者是用户名和密码给客户端浏览器,浏览器会将该信息保存到上述某一个当中,如果没有同源策略,恶意网站就可以通过脚本获得用户的数据
无法接触非同源网页的 DOM
来自一个源的JS只能读写自己源的DOM树。如果可跨源读写DOM的话,在页面里面使用iframe嵌入一个银行页面,就可以随意篡改银行页面的内容了。
无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)
一般而言,JS只能向同源的接口发送请求。如果向其它源发送请求时,一方面,浏览器发现JS向其它源的接口发送请求时会自动带上Origin头来标识来源,让服务器能够根据Origin判断要不要响应;另一方面,浏览器在接收到响应后,如果没有发现Access-Control-Allow-Origin允许发送请求的域进行请求,浏览器就不允许解析。
另外,通过 JavaScript 脚本可以拿到其他窗口的window
对象,比如 iframe节点的window对象
二、跨域
指由于浏览器同源策略,浏览器对其它域执行一些操作时会受到限制。下面将介绍如何去规避这些限制
在浏览器中,<script>
、<img>
、<iframe>
、<link>
这几个标签可以跨域加载,而不受浏览器的同源策略控制,这些带src
属性的标签实际上是通过浏览器发送GET
请求来加载。不同于普通请求(XMLHttpRequest)的是,通过src
加载的资源,浏览器限制了JS对它们的读写权限,使其不能读写通过src
加载返回的内容
除了上述几个标签,其它跨越请求,请求都会发送到跨域的服务器,并且服务器会返回数据,只不过浏览器“拒收”返回的数据
1、规避cookie的限制
一般cookie的限制出现在一级域名和次级域名之间
限制描述
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。如果不做设置,次级域名无法与一级域名共享cookie
规避办法
通过设置document.domain
共享 Cookie。
举例来说,A 网页的网址是http://w1.example.com/a.html
,B 网页的网址是http://w2.example.com/b.html
,那么只要设置相同的document.domain
,两个网页就可以共享 Cookie。因为浏览器通过document.domain
属性来检查是否同源。
1 |
|
这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexedDB 无法通过这种方法,规避同源政策
另外,服务器也可以在设置 Cookie 的时候,指定 Cookie 的所属域名为一级域名,比如.example.com
。
1 |
|
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个 Cookie。
2、iframe和多窗口
一定层度上可以当做是规避DOM限制,但又不局限于DOM限制
限制描述
iframe
元素可以在当前网页之中,嵌入其他网页。每个iframe
元素形成自己的窗口,即有自己的window
对象。iframe
窗口之中的脚本,可以获得父窗口和子窗口。但是,只有在同源的情况下,父窗口和子窗口才能通信;如果跨域,就无法拿到对方的 DOM。
比如,父窗口运行下面的命令,如果iframe
窗口不是同源,就会报错。
1 |
|
规避办法
- document.domain,如果两个窗口只是二级域名不同,可以通过上面提到的办法规避
- 片段识别符,通过设置锚点,在URL的锚点里放置要传递的信息,通过修改iframe的src的方法(不会触发刷新),传递信息
- 跨文档通信API —— postMessage()
postMessage()
HTML5 引入了一个API 跨文档通信 API(Cross-document messaging),这个 API 为window
对象新增了一个window.postMessage
方法,允许跨窗口通信,不论这两个窗口是否同源。
- 参数一:具体的信息内容
- 参数二:接受信息的窗口的源。如果是
*
,向所有窗口发送
1 |
|
父窗口和子窗口都可以通过message
事件,监听对方的消息
1 |
|
message
事件的参数是事件对象event
,提供以下三个属性:
event.source
:发送消息的窗口(引用值),这使得读写其它窗口的LocalStorage也成为可能event.origin
: 消息发向的网址event.data
: 消息内容
3、规避AJAX的限制
同源政策规定,AJAX 请求只能发给同源的网址,否则就报错
规避办法:
- 代理服务器,浏览器请求同源服务器,再由同源服务器请求外部服务(服务器没有同源策略一说)
- JSONP,借助
<script>
标签不受同源策略限制的特点,通过 脚本代码传递数据 - WebSocket,通信协议,不实行同源策略
- CORS
JSONP
最大特点就是简单易用,没有兼容性问题,老式浏览器全部支持,服务端改造非常小
做法:
网页添加一个
<script>
元素,向服务器请求一个脚本,这不受同源政策限制,可以跨域请求1
2<script src="http://api.foo.com?callback=bar"></script>
/*注意,请求的脚本网址有一个callback参数(?callback=bar),用来告诉服务器,客户端的回调函数名称*/服务器收到请求后,拼接一个字符串,将 JSON 数据放在函数名里面,作为字符串返回(
bar({...})
)客户端会将服务器返回的字符串,作为代码解析(因为浏览器认为,这是
<script>
标签请求的脚本内容)。这时,客户端只要定义了bar()
函数,就能在该函数体内,拿到服务器返回的 JSON 数据
示例:
1 |
|
1 |
|
由于<script>
元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo
函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用JSON.parse
的步骤。
WebSocket
WebSocket 是一种通信协议,使用ws://
(非加密)和wss://
(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。是server push技术的一种很好的实现
下面是一个例子,浏览器发出的 WebSocket 请求的头信息
1 |
|
上面代码中,有一个字段是Origin
,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin
这个字段,所以 WebSocket 才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。
1 |
|
CORS
CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET
请求,CORS 允许任何类型的请求